@neat.is/core 0.10.1 → 0.10.3-dev.20260930
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-AWIK6T3G.js → chunk-DLQNMU3R.js} +2 -2
- package/dist/{chunk-E3YFFIHE.js → chunk-LNIVRE4L.js} +801 -569
- package/dist/chunk-LNIVRE4L.js.map +1 -0
- package/dist/cli.cjs +1544 -1081
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +5 -2
- package/dist/cli.d.ts +5 -2
- package/dist/cli.js +457 -224
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +843 -628
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -1
- package/dist/index.d.ts +33 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +854 -639
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +712 -525
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-E3YFFIHE.js.map +0 -1
- /package/dist/{chunk-AWIK6T3G.js.map → chunk-DLQNMU3R.js.map} +0 -0
|
@@ -342,8 +342,8 @@ function deprecatedApis() {
|
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
// src/ingest.ts
|
|
345
|
-
import { promises as
|
|
346
|
-
import
|
|
345
|
+
import { promises as fs8, existsSync, readFileSync } from "fs";
|
|
346
|
+
import path9 from "path";
|
|
347
347
|
import * as sourceMapJs from "source-map-js";
|
|
348
348
|
|
|
349
349
|
// src/policy.ts
|
|
@@ -942,7 +942,7 @@ import {
|
|
|
942
942
|
} from "@neat.is/types";
|
|
943
943
|
|
|
944
944
|
// src/extract/routes.ts
|
|
945
|
-
import
|
|
945
|
+
import path8 from "path";
|
|
946
946
|
import Parser2 from "tree-sitter";
|
|
947
947
|
import JavaScript2 from "tree-sitter-javascript";
|
|
948
948
|
import Python2 from "tree-sitter-python";
|
|
@@ -974,6 +974,17 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
974
974
|
"dist",
|
|
975
975
|
"build",
|
|
976
976
|
".next",
|
|
977
|
+
// `_next` is the *emitted* half of the pair above — `next build`'s output as
|
|
978
|
+
// it is served or exported. `.next` was listed and `_next` wasn't, so an
|
|
979
|
+
// exported bundle walked in as first-party source: minified chunks, one file
|
|
980
|
+
// per line, hundreds of KB each (#1254).
|
|
981
|
+
"_next",
|
|
982
|
+
// Agent scratch space. `.claude/worktrees/` holds a full checkout per agent
|
|
983
|
+
// session, so walking it re-extracts the whole repo once per worktree — and
|
|
984
|
+
// pulls in whatever build output those checkouts happen to carry. It is
|
|
985
|
+
// gitignored in practice, but the file walk doesn't read .gitignore, so the
|
|
986
|
+
// name has to be listed here.
|
|
987
|
+
".claude",
|
|
977
988
|
// Python virtualenv shapes (issue #344). Walking into a venv pulls in the
|
|
978
989
|
// entire CPython stdlib + every installed package as if it were first-party
|
|
979
990
|
// service code — 20k+ files, none of which the user wrote. The shape names
|
|
@@ -1212,6 +1223,20 @@ function noteExtractedDropped(edge) {
|
|
|
1212
1223
|
function drainDroppedExtracted() {
|
|
1213
1224
|
return droppedSink.splice(0, droppedSink.length);
|
|
1214
1225
|
}
|
|
1226
|
+
var skippedSink = /* @__PURE__ */ new Map();
|
|
1227
|
+
function noteSkippedFile(file) {
|
|
1228
|
+
if (!skippedSink.has(file.path)) skippedSink.set(file.path, file);
|
|
1229
|
+
}
|
|
1230
|
+
function drainSkippedFiles() {
|
|
1231
|
+
const out = [...skippedSink.values()];
|
|
1232
|
+
skippedSink.clear();
|
|
1233
|
+
return out;
|
|
1234
|
+
}
|
|
1235
|
+
function formatSkippedBanner(count) {
|
|
1236
|
+
if (count <= 0) return null;
|
|
1237
|
+
if (count === 1) return `[neat] 1 minified file read but not parsed (FileNode kept, no symbols or calls)`;
|
|
1238
|
+
return `[neat] ${count} minified files read but not parsed (FileNodes kept, no symbols or calls)`;
|
|
1239
|
+
}
|
|
1215
1240
|
function isRejectedLogEnabled() {
|
|
1216
1241
|
const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG;
|
|
1217
1242
|
return raw === "1" || raw === "true";
|
|
@@ -1228,8 +1253,8 @@ function formatPrecisionFloorBanner(count) {
|
|
|
1228
1253
|
}
|
|
1229
1254
|
|
|
1230
1255
|
// src/extract/calls/shared.ts
|
|
1231
|
-
import { promises as
|
|
1232
|
-
import
|
|
1256
|
+
import { promises as fs6 } from "fs";
|
|
1257
|
+
import path6 from "path";
|
|
1233
1258
|
import {
|
|
1234
1259
|
EdgeType as EdgeType2,
|
|
1235
1260
|
NodeType as NodeType2,
|
|
@@ -1238,11 +1263,87 @@ import {
|
|
|
1238
1263
|
extractedEdgeId as extractedEdgeId2,
|
|
1239
1264
|
fileId
|
|
1240
1265
|
} from "@neat.is/types";
|
|
1266
|
+
|
|
1267
|
+
// src/extract/gitignore.ts
|
|
1268
|
+
import { promises as fs5 } from "fs";
|
|
1269
|
+
import path5 from "path";
|
|
1270
|
+
import ignore from "ignore";
|
|
1271
|
+
var LAYER_CACHE = /* @__PURE__ */ new Map();
|
|
1272
|
+
async function layerFor(dir) {
|
|
1273
|
+
const cached = LAYER_CACHE.get(dir);
|
|
1274
|
+
if (cached !== void 0) return cached;
|
|
1275
|
+
let layer = null;
|
|
1276
|
+
try {
|
|
1277
|
+
const raw = await fs5.readFile(path5.join(dir, ".gitignore"), "utf8");
|
|
1278
|
+
layer = { dir, ig: ignore().add(raw) };
|
|
1279
|
+
} catch {
|
|
1280
|
+
layer = null;
|
|
1281
|
+
}
|
|
1282
|
+
LAYER_CACHE.set(dir, layer);
|
|
1283
|
+
return layer;
|
|
1284
|
+
}
|
|
1285
|
+
async function isRepoRoot(dir) {
|
|
1286
|
+
try {
|
|
1287
|
+
await fs5.stat(path5.join(dir, ".git"));
|
|
1288
|
+
return true;
|
|
1289
|
+
} catch {
|
|
1290
|
+
return false;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
async function loadIgnoreChain(startDir) {
|
|
1294
|
+
const dirs = [];
|
|
1295
|
+
let current = path5.resolve(startDir);
|
|
1296
|
+
for (; ; ) {
|
|
1297
|
+
dirs.push(current);
|
|
1298
|
+
if (await isRepoRoot(current)) break;
|
|
1299
|
+
const parent = path5.dirname(current);
|
|
1300
|
+
if (parent === current) break;
|
|
1301
|
+
current = parent;
|
|
1302
|
+
}
|
|
1303
|
+
dirs.reverse();
|
|
1304
|
+
const layers = [];
|
|
1305
|
+
for (const dir of dirs) {
|
|
1306
|
+
const layer = await layerFor(dir);
|
|
1307
|
+
if (layer) layers.push(layer);
|
|
1308
|
+
}
|
|
1309
|
+
return layers;
|
|
1310
|
+
}
|
|
1311
|
+
async function extendIgnoreChain(chain, dir) {
|
|
1312
|
+
const layer = await layerFor(path5.resolve(dir));
|
|
1313
|
+
return layer ? [...chain, layer] : chain;
|
|
1314
|
+
}
|
|
1315
|
+
function isIgnored(chain, fullPath, isDir) {
|
|
1316
|
+
return decide(chain, fullPath, isDir).ignored;
|
|
1317
|
+
}
|
|
1318
|
+
function decide(chain, fullPath, isDir) {
|
|
1319
|
+
if (chain.length === 0) return { ignored: false, subtree: chain };
|
|
1320
|
+
const abs = path5.resolve(fullPath);
|
|
1321
|
+
let ignored = false;
|
|
1322
|
+
let unignoredAt = -1;
|
|
1323
|
+
for (let i = 0; i < chain.length; i++) {
|
|
1324
|
+
const layer = chain[i];
|
|
1325
|
+
const rel = path5.relative(layer.dir, abs);
|
|
1326
|
+
if (!rel || rel.startsWith("..") || path5.isAbsolute(rel)) continue;
|
|
1327
|
+
const candidate = rel.split(path5.sep).join("/") + (isDir ? "/" : "");
|
|
1328
|
+
const result = layer.ig.test(candidate);
|
|
1329
|
+
if (result.ignored) ignored = true;
|
|
1330
|
+
else if (result.unignored) {
|
|
1331
|
+
ignored = false;
|
|
1332
|
+
unignoredAt = i;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
return {
|
|
1336
|
+
ignored,
|
|
1337
|
+
subtree: unignoredAt >= 0 ? chain.slice(unignoredAt) : chain
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// src/extract/calls/shared.ts
|
|
1241
1342
|
function buildServiceHostIndex(services) {
|
|
1242
1343
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
1243
1344
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
1244
1345
|
for (const service of services) {
|
|
1245
|
-
const base =
|
|
1346
|
+
const base = path6.basename(service.dir);
|
|
1246
1347
|
knownHosts.add(base);
|
|
1247
1348
|
knownHosts.add(service.pkg.name);
|
|
1248
1349
|
hostToNodeId.set(base, service.node.id);
|
|
@@ -1251,33 +1352,62 @@ function buildServiceHostIndex(services) {
|
|
|
1251
1352
|
return { knownHosts, hostToNodeId };
|
|
1252
1353
|
}
|
|
1253
1354
|
async function walkSourceFiles(dir, excludeDirs = []) {
|
|
1254
|
-
const excluded = new Set(excludeDirs.map((d) =>
|
|
1355
|
+
const excluded = new Set(excludeDirs.map((d) => path6.resolve(d)));
|
|
1255
1356
|
const out = [];
|
|
1256
|
-
|
|
1257
|
-
|
|
1357
|
+
const rootChain = await loadIgnoreChain(dir);
|
|
1358
|
+
async function walk11(current, inherited) {
|
|
1359
|
+
const entries = await fs6.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
1360
|
+
const chain = entries.some((e) => e.isFile() && e.name === ".gitignore") ? await extendIgnoreChain(inherited, current) : inherited;
|
|
1258
1361
|
for (const entry of entries) {
|
|
1259
|
-
const full =
|
|
1362
|
+
const full = path6.join(current, entry.name);
|
|
1260
1363
|
if (entry.isDirectory()) {
|
|
1261
1364
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1262
|
-
if (excluded.has(
|
|
1365
|
+
if (excluded.has(path6.resolve(full))) continue;
|
|
1366
|
+
const verdict = decide(chain, full, true);
|
|
1367
|
+
if (verdict.ignored) continue;
|
|
1263
1368
|
if (await isPythonVenvDir(full)) continue;
|
|
1264
|
-
await walk11(full);
|
|
1265
|
-
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(
|
|
1369
|
+
await walk11(full, verdict.subtree);
|
|
1370
|
+
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path6.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
|
|
1266
1371
|
// would attribute our instrumentation imports to the user's service.
|
|
1267
|
-
!isNeatAuthoredSourceFile(entry.name)) {
|
|
1372
|
+
!isNeatAuthoredSourceFile(entry.name) && !isIgnored(chain, full, false)) {
|
|
1268
1373
|
out.push(full);
|
|
1269
1374
|
}
|
|
1270
1375
|
}
|
|
1271
1376
|
}
|
|
1272
|
-
await walk11(dir);
|
|
1377
|
+
await walk11(dir, rootChain.filter((l) => path6.resolve(l.dir) !== path6.resolve(dir)));
|
|
1273
1378
|
return out;
|
|
1274
1379
|
}
|
|
1380
|
+
var MINIFIED_LINE_CHARS = 5e3;
|
|
1381
|
+
function hasMinifiedName(filePath) {
|
|
1382
|
+
return /\.min\.(js|mjs|cjs|jsx|ts|tsx)$/i.test(filePath);
|
|
1383
|
+
}
|
|
1384
|
+
function isMinifiedSource(filePath, content) {
|
|
1385
|
+
if (hasMinifiedName(filePath)) return { minified: true, detail: "named *.min.js" };
|
|
1386
|
+
let longest = 0;
|
|
1387
|
+
let start = 0;
|
|
1388
|
+
for (; ; ) {
|
|
1389
|
+
const nl = content.indexOf("\n", start);
|
|
1390
|
+
const len = (nl === -1 ? content.length : nl) - start;
|
|
1391
|
+
if (len > longest) longest = len;
|
|
1392
|
+
if (nl === -1) break;
|
|
1393
|
+
start = nl + 1;
|
|
1394
|
+
}
|
|
1395
|
+
if (longest > MINIFIED_LINE_CHARS) {
|
|
1396
|
+
return { minified: true, detail: `longest line ${longest} chars` };
|
|
1397
|
+
}
|
|
1398
|
+
return { minified: false, detail: "" };
|
|
1399
|
+
}
|
|
1275
1400
|
async function loadSourceFiles(dir, excludeDirs = []) {
|
|
1276
1401
|
const paths = await walkSourceFiles(dir, excludeDirs);
|
|
1277
1402
|
const out = [];
|
|
1278
1403
|
for (const p of paths) {
|
|
1279
1404
|
try {
|
|
1280
|
-
const content = await
|
|
1405
|
+
const content = await fs6.readFile(p, "utf8");
|
|
1406
|
+
const verdict = isMinifiedSource(p, content);
|
|
1407
|
+
if (verdict.minified) {
|
|
1408
|
+
noteSkippedFile({ path: p, reason: "minified", detail: verdict.detail });
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1281
1411
|
out.push({ path: p, content });
|
|
1282
1412
|
} catch {
|
|
1283
1413
|
}
|
|
@@ -1289,15 +1419,18 @@ function lineOf(text, needle) {
|
|
|
1289
1419
|
if (idx < 0) return 1;
|
|
1290
1420
|
return text.slice(0, idx).split("\n").length;
|
|
1291
1421
|
}
|
|
1422
|
+
var SNIPPET_MAX_CHARS = 120;
|
|
1292
1423
|
function snippet(text, line) {
|
|
1293
1424
|
const lines = text.split("\n");
|
|
1294
|
-
|
|
1425
|
+
const raw = (lines[line - 1] ?? "").trim();
|
|
1426
|
+
if (raw.length <= SNIPPET_MAX_CHARS) return raw;
|
|
1427
|
+
return `${raw.slice(0, SNIPPET_MAX_CHARS)}\u2026 (+${raw.length - SNIPPET_MAX_CHARS} chars)`;
|
|
1295
1428
|
}
|
|
1296
1429
|
function toPosix(p) {
|
|
1297
1430
|
return p.split("\\").join("/");
|
|
1298
1431
|
}
|
|
1299
1432
|
function languageForPath(relPath) {
|
|
1300
|
-
switch (
|
|
1433
|
+
switch (path6.extname(relPath).toLowerCase()) {
|
|
1301
1434
|
case ".py":
|
|
1302
1435
|
return "python";
|
|
1303
1436
|
case ".go":
|
|
@@ -1370,8 +1503,8 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
|
1370
1503
|
}
|
|
1371
1504
|
|
|
1372
1505
|
// src/extract/imports.ts
|
|
1373
|
-
import
|
|
1374
|
-
import { promises as
|
|
1506
|
+
import path7 from "path";
|
|
1507
|
+
import { promises as fs7 } from "fs";
|
|
1375
1508
|
import Parser from "tree-sitter";
|
|
1376
1509
|
import JavaScript from "tree-sitter-javascript";
|
|
1377
1510
|
import Python from "tree-sitter-python";
|
|
@@ -1522,15 +1655,15 @@ function collectPyImports(node, out) {
|
|
|
1522
1655
|
}
|
|
1523
1656
|
async function fileExists(p) {
|
|
1524
1657
|
try {
|
|
1525
|
-
await
|
|
1658
|
+
await fs7.access(p);
|
|
1526
1659
|
return true;
|
|
1527
1660
|
} catch {
|
|
1528
1661
|
return false;
|
|
1529
1662
|
}
|
|
1530
1663
|
}
|
|
1531
1664
|
function isWithinServiceDir(candidate, serviceDir) {
|
|
1532
|
-
const rel =
|
|
1533
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
1665
|
+
const rel = path7.relative(serviceDir, candidate);
|
|
1666
|
+
return rel !== "" && !rel.startsWith("..") && !path7.isAbsolute(rel);
|
|
1534
1667
|
}
|
|
1535
1668
|
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1536
1669
|
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
@@ -1538,22 +1671,22 @@ async function firstExistingCandidate(base, serviceDir) {
|
|
|
1538
1671
|
for (const ext of JS_EXTENSIONS) {
|
|
1539
1672
|
const candidate = base + ext;
|
|
1540
1673
|
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
1541
|
-
return toPosix(
|
|
1674
|
+
return toPosix(path7.relative(serviceDir, candidate));
|
|
1542
1675
|
}
|
|
1543
1676
|
}
|
|
1544
1677
|
for (const indexFile of JS_INDEX_FILES) {
|
|
1545
|
-
const candidate =
|
|
1678
|
+
const candidate = path7.join(base, indexFile);
|
|
1546
1679
|
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
1547
|
-
return toPosix(
|
|
1680
|
+
return toPosix(path7.relative(serviceDir, candidate));
|
|
1548
1681
|
}
|
|
1549
1682
|
}
|
|
1550
1683
|
return null;
|
|
1551
1684
|
}
|
|
1552
1685
|
async function loadTsPathConfig(serviceDir) {
|
|
1553
|
-
const tsconfigPath =
|
|
1686
|
+
const tsconfigPath = path7.join(serviceDir, "tsconfig.json");
|
|
1554
1687
|
let raw;
|
|
1555
1688
|
try {
|
|
1556
|
-
raw = await
|
|
1689
|
+
raw = await fs7.readFile(tsconfigPath, "utf8");
|
|
1557
1690
|
} catch {
|
|
1558
1691
|
return null;
|
|
1559
1692
|
}
|
|
@@ -1562,7 +1695,7 @@ async function loadTsPathConfig(serviceDir) {
|
|
|
1562
1695
|
const paths = parsed.compilerOptions?.paths;
|
|
1563
1696
|
if (!paths || Object.keys(paths).length === 0) return null;
|
|
1564
1697
|
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
1565
|
-
return { paths, baseDir: baseUrl ?
|
|
1698
|
+
return { paths, baseDir: baseUrl ? path7.resolve(serviceDir, baseUrl) : serviceDir };
|
|
1566
1699
|
} catch (err) {
|
|
1567
1700
|
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
1568
1701
|
return null;
|
|
@@ -1580,11 +1713,11 @@ async function resolveTsAlias(specifier, config, serviceDir) {
|
|
|
1580
1713
|
if (suffix === null) continue;
|
|
1581
1714
|
for (const target of targets) {
|
|
1582
1715
|
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
1583
|
-
const resolvedBase =
|
|
1716
|
+
const resolvedBase = path7.resolve(config.baseDir, targetBase, suffix);
|
|
1584
1717
|
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
1585
1718
|
if (hit) return hit;
|
|
1586
1719
|
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
|
|
1587
|
-
return toPosix(
|
|
1720
|
+
return toPosix(path7.relative(serviceDir, resolvedBase));
|
|
1588
1721
|
}
|
|
1589
1722
|
}
|
|
1590
1723
|
}
|
|
@@ -1593,18 +1726,18 @@ async function resolveTsAlias(specifier, config, serviceDir) {
|
|
|
1593
1726
|
async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
|
|
1594
1727
|
if (!specifier) return null;
|
|
1595
1728
|
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
1596
|
-
const base =
|
|
1597
|
-
const ext =
|
|
1729
|
+
const base = path7.resolve(importerDir, specifier);
|
|
1730
|
+
const ext = path7.extname(specifier);
|
|
1598
1731
|
if (ext) {
|
|
1599
1732
|
if (ext === ".js" || ext === ".jsx") {
|
|
1600
1733
|
const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
|
|
1601
1734
|
const tsSibling = base.slice(0, -ext.length) + tsExt;
|
|
1602
1735
|
if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
|
|
1603
|
-
return toPosix(
|
|
1736
|
+
return toPosix(path7.relative(serviceDir, tsSibling));
|
|
1604
1737
|
}
|
|
1605
1738
|
}
|
|
1606
1739
|
if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
|
|
1607
|
-
return toPosix(
|
|
1740
|
+
return toPosix(path7.relative(serviceDir, base));
|
|
1608
1741
|
}
|
|
1609
1742
|
if (!JS_EXTENSIONS.includes(ext)) {
|
|
1610
1743
|
return firstExistingCandidate(base, serviceDir);
|
|
@@ -1619,30 +1752,30 @@ async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
|
|
|
1619
1752
|
async function resolvePyImport(imp, importerPath, serviceDir) {
|
|
1620
1753
|
let baseDir;
|
|
1621
1754
|
if (imp.level > 0) {
|
|
1622
|
-
baseDir =
|
|
1623
|
-
for (let i = 1; i < imp.level; i++) baseDir =
|
|
1755
|
+
baseDir = path7.dirname(importerPath);
|
|
1756
|
+
for (let i = 1; i < imp.level; i++) baseDir = path7.dirname(baseDir);
|
|
1624
1757
|
} else {
|
|
1625
1758
|
baseDir = serviceDir;
|
|
1626
1759
|
}
|
|
1627
|
-
const moduleBase = imp.modulePath ?
|
|
1760
|
+
const moduleBase = imp.modulePath ? path7.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
|
|
1628
1761
|
const resolved = /* @__PURE__ */ new Set();
|
|
1629
1762
|
let needModuleFile = imp.names.length === 0;
|
|
1630
1763
|
for (const name of imp.names) {
|
|
1631
|
-
const submoduleFile =
|
|
1632
|
-
const subpackageInit =
|
|
1764
|
+
const submoduleFile = path7.join(moduleBase, `${name}.py`);
|
|
1765
|
+
const subpackageInit = path7.join(moduleBase, name, "__init__.py");
|
|
1633
1766
|
if (isWithinServiceDir(submoduleFile, serviceDir) && await fileExists(submoduleFile)) {
|
|
1634
|
-
resolved.add(toPosix(
|
|
1767
|
+
resolved.add(toPosix(path7.relative(serviceDir, submoduleFile)));
|
|
1635
1768
|
} else if (isWithinServiceDir(subpackageInit, serviceDir) && await fileExists(subpackageInit)) {
|
|
1636
|
-
resolved.add(toPosix(
|
|
1769
|
+
resolved.add(toPosix(path7.relative(serviceDir, subpackageInit)));
|
|
1637
1770
|
} else {
|
|
1638
1771
|
needModuleFile = true;
|
|
1639
1772
|
}
|
|
1640
1773
|
}
|
|
1641
1774
|
if (needModuleFile) {
|
|
1642
|
-
const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`,
|
|
1775
|
+
const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, path7.join(moduleBase, "__init__.py")] : [path7.join(moduleBase, "__init__.py")];
|
|
1643
1776
|
for (const candidate of moduleFileCandidates) {
|
|
1644
1777
|
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
1645
|
-
resolved.add(toPosix(
|
|
1778
|
+
resolved.add(toPosix(path7.relative(serviceDir, candidate)));
|
|
1646
1779
|
break;
|
|
1647
1780
|
}
|
|
1648
1781
|
}
|
|
@@ -1652,11 +1785,11 @@ async function resolvePyImport(imp, importerPath, serviceDir) {
|
|
|
1652
1785
|
async function resolveGoImport(specifier, modulePath, serviceDir) {
|
|
1653
1786
|
if (specifier !== modulePath && !specifier.startsWith(`${modulePath}/`)) return null;
|
|
1654
1787
|
const suffix = specifier === modulePath ? "" : specifier.slice(modulePath.length + 1);
|
|
1655
|
-
const dir =
|
|
1656
|
-
const entries = await
|
|
1657
|
-
const candidates = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".go") && !entry.name.endsWith("_test.go")).map((entry) =>
|
|
1788
|
+
const dir = path7.join(serviceDir, suffix);
|
|
1789
|
+
const entries = await fs7.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1790
|
+
const candidates = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".go") && !entry.name.endsWith("_test.go")).map((entry) => path7.join(dir, entry.name));
|
|
1658
1791
|
if (candidates.length !== 1) return null;
|
|
1659
|
-
return toPosix(
|
|
1792
|
+
return toPosix(path7.relative(serviceDir, candidates[0]));
|
|
1660
1793
|
}
|
|
1661
1794
|
function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
|
|
1662
1795
|
const importeeFileId = fileId2(serviceName, importeeRelPath);
|
|
@@ -1686,12 +1819,12 @@ async function addImports(graph, services) {
|
|
|
1686
1819
|
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
1687
1820
|
for (const file of files) {
|
|
1688
1821
|
if (isTestPath(file.path)) continue;
|
|
1689
|
-
const relFile = toPosix(
|
|
1822
|
+
const relFile = toPosix(path7.relative(service.dir, file.path));
|
|
1690
1823
|
const importerFileId = fileId2(service.pkg.name, relFile);
|
|
1691
|
-
const isPython =
|
|
1692
|
-
const isGo =
|
|
1693
|
-
if (
|
|
1694
|
-
if (
|
|
1824
|
+
const isPython = path7.extname(file.path) === ".py";
|
|
1825
|
+
const isGo = path7.extname(file.path) === ".go";
|
|
1826
|
+
if (path7.extname(file.path) === ".rb") continue;
|
|
1827
|
+
if (path7.extname(file.path) === ".php") continue;
|
|
1695
1828
|
if (isGo) {
|
|
1696
1829
|
let goImports = [];
|
|
1697
1830
|
try {
|
|
@@ -1701,7 +1834,7 @@ async function addImports(graph, services) {
|
|
|
1701
1834
|
recordExtractionError("import extraction", file.path, err);
|
|
1702
1835
|
continue;
|
|
1703
1836
|
}
|
|
1704
|
-
const goMod = await
|
|
1837
|
+
const goMod = await fs7.readFile(path7.join(service.dir, "go.mod"), "utf8").catch(() => "");
|
|
1705
1838
|
const modulePath = goMod.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
|
|
1706
1839
|
if (!modulePath) continue;
|
|
1707
1840
|
for (const imp of goImports) {
|
|
@@ -1745,7 +1878,7 @@ async function addImports(graph, services) {
|
|
|
1745
1878
|
continue;
|
|
1746
1879
|
}
|
|
1747
1880
|
for (const imp of jsImports) {
|
|
1748
|
-
const resolved = await resolveJsImport(imp.specifier,
|
|
1881
|
+
const resolved = await resolveJsImport(imp.specifier, path7.dirname(file.path), service.dir, tsPaths);
|
|
1749
1882
|
if (!resolved) continue;
|
|
1750
1883
|
edgesAdded += emitImportEdge(
|
|
1751
1884
|
graph,
|
|
@@ -1870,8 +2003,8 @@ function chiRoutesFromSource(source, parser) {
|
|
|
1870
2003
|
chiWalk(tree.rootNode, "", out);
|
|
1871
2004
|
return out;
|
|
1872
2005
|
}
|
|
1873
|
-
function stripChiRegex(
|
|
1874
|
-
return
|
|
2006
|
+
function stripChiRegex(path74) {
|
|
2007
|
+
return path74.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
|
|
1875
2008
|
}
|
|
1876
2009
|
function chiWalk(node, prefix, out) {
|
|
1877
2010
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -2211,7 +2344,7 @@ function isNextPagesApiFile(relFile) {
|
|
|
2211
2344
|
if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
|
|
2212
2345
|
const base = segs[segs.length - 1] ?? "";
|
|
2213
2346
|
if (/^_(app|document|middleware)\./.test(base)) return false;
|
|
2214
|
-
return JS_ROUTE_EXTENSIONS.has(
|
|
2347
|
+
return JS_ROUTE_EXTENSIONS.has(path8.extname(base));
|
|
2215
2348
|
}
|
|
2216
2349
|
function nextSegment(seg) {
|
|
2217
2350
|
if (seg.startsWith("(") && seg.endsWith(")")) return null;
|
|
@@ -2543,9 +2676,9 @@ function rubyRocketRoute(args) {
|
|
|
2543
2676
|
if (!pair || pair.type !== "pair") continue;
|
|
2544
2677
|
const k = pair.childForFieldName("key");
|
|
2545
2678
|
if (k?.type !== "string") continue;
|
|
2546
|
-
const
|
|
2547
|
-
if (
|
|
2548
|
-
return { path:
|
|
2679
|
+
const path74 = rubyLiteral(k);
|
|
2680
|
+
if (path74 === null) continue;
|
|
2681
|
+
return { path: path74, target: rubyLiteral(pair.childForFieldName("value")) };
|
|
2549
2682
|
}
|
|
2550
2683
|
return null;
|
|
2551
2684
|
}
|
|
@@ -3393,12 +3526,12 @@ async function expressMountPrefixes(files, serviceDir, tsPaths) {
|
|
|
3393
3526
|
const jsParser = makeJsParser2();
|
|
3394
3527
|
const fileInfo = /* @__PURE__ */ new Map();
|
|
3395
3528
|
for (const f of files) {
|
|
3396
|
-
if (!JS_ROUTE_EXTENSIONS.has(
|
|
3529
|
+
if (!JS_ROUTE_EXTENSIONS.has(path8.extname(f.path))) continue;
|
|
3397
3530
|
if (isTestPath(f.path)) continue;
|
|
3398
|
-
const rel = toPosix(
|
|
3531
|
+
const rel = toPosix(path8.relative(serviceDir, f.path));
|
|
3399
3532
|
try {
|
|
3400
3533
|
const tree = parseSource2(jsParser, f.content);
|
|
3401
|
-
fileInfo.set(rel, analyzeExpressFile(tree.rootNode,
|
|
3534
|
+
fileInfo.set(rel, analyzeExpressFile(tree.rootNode, path8.dirname(f.path)));
|
|
3402
3535
|
} catch {
|
|
3403
3536
|
}
|
|
3404
3537
|
}
|
|
@@ -3497,14 +3630,14 @@ async function addRoutes(graph, services) {
|
|
|
3497
3630
|
const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
|
|
3498
3631
|
for (const file of files) {
|
|
3499
3632
|
if (isTestPath(file.path)) continue;
|
|
3500
|
-
const ext =
|
|
3633
|
+
const ext = path8.extname(file.path);
|
|
3501
3634
|
const isPy = ext === ".py";
|
|
3502
3635
|
const isGo = ext === ".go";
|
|
3503
3636
|
const isRb = ext === ".rb";
|
|
3504
3637
|
const isPhp = ext === ".php";
|
|
3505
3638
|
const isRs = ext === ".rs";
|
|
3506
3639
|
if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy && !isGo && !isRb && !isPhp && !isRs) continue;
|
|
3507
|
-
const relFile = toPosix(
|
|
3640
|
+
const relFile = toPosix(path8.relative(service.dir, file.path));
|
|
3508
3641
|
let routes;
|
|
3509
3642
|
try {
|
|
3510
3643
|
if (isPhp) {
|
|
@@ -4009,7 +4142,7 @@ function languageForExt(relPath) {
|
|
|
4009
4142
|
function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
|
|
4010
4143
|
let p = toPosix2(filepath).replace(/^file:\/\//, "");
|
|
4011
4144
|
if (scanPath && scanPath.length > 0) {
|
|
4012
|
-
const absRoot = toPosix2(
|
|
4145
|
+
const absRoot = toPosix2(path9.resolve(scanPath, serviceNode?.repoPath ?? ""));
|
|
4013
4146
|
const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
|
|
4014
4147
|
if (p.startsWith(anchor)) return p.slice(anchor.length);
|
|
4015
4148
|
}
|
|
@@ -4040,7 +4173,7 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
4040
4173
|
if (existsSync(mapPath)) {
|
|
4041
4174
|
const raw = JSON.parse(readFileSync(mapPath, "utf8"));
|
|
4042
4175
|
const consumer = new sourceMapJs.SourceMapConsumer(raw);
|
|
4043
|
-
entry = { consumer, dir:
|
|
4176
|
+
entry = { consumer, dir: path9.dirname(mapPath) };
|
|
4044
4177
|
}
|
|
4045
4178
|
} catch {
|
|
4046
4179
|
entry = null;
|
|
@@ -4060,7 +4193,7 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
4060
4193
|
}
|
|
4061
4194
|
if (!pos || !pos.source) return null;
|
|
4062
4195
|
const root = entry.consumer.sourceRoot ?? "";
|
|
4063
|
-
const resolved =
|
|
4196
|
+
const resolved = path9.resolve(entry.dir, root, pos.source);
|
|
4064
4197
|
return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
|
|
4065
4198
|
} catch {
|
|
4066
4199
|
return null;
|
|
@@ -4604,8 +4737,8 @@ function emitIncidentEvent(project, ev) {
|
|
|
4604
4737
|
});
|
|
4605
4738
|
}
|
|
4606
4739
|
async function appendErrorEvent(ctx, ev) {
|
|
4607
|
-
await
|
|
4608
|
-
await
|
|
4740
|
+
await fs8.mkdir(path9.dirname(ctx.errorsPath), { recursive: true });
|
|
4741
|
+
await fs8.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
4609
4742
|
emitIncidentEvent(ctx.project ?? DEFAULT_PROJECT, ev);
|
|
4610
4743
|
}
|
|
4611
4744
|
async function appendConnectorIncident(errorsPath, input, project) {
|
|
@@ -4620,8 +4753,8 @@ async function appendConnectorIncident(errorsPath, input, project) {
|
|
|
4620
4753
|
...input.attributes && Object.keys(input.attributes).length > 0 ? { attributes: input.attributes } : {},
|
|
4621
4754
|
affectedNode: input.affectedNode
|
|
4622
4755
|
};
|
|
4623
|
-
await
|
|
4624
|
-
await
|
|
4756
|
+
await fs8.mkdir(path9.dirname(errorsPath), { recursive: true });
|
|
4757
|
+
await fs8.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
4625
4758
|
if (project) emitIncidentEvent(project, ev);
|
|
4626
4759
|
}
|
|
4627
4760
|
function landIncidentCallSite(span, callSite, trusted, graph) {
|
|
@@ -4743,8 +4876,8 @@ function makeErrorSpanWriter(errorsPath, graph, scanPath, project = DEFAULT_PROJ
|
|
|
4743
4876
|
return async (span) => {
|
|
4744
4877
|
const ev = buildErrorEventForReceiver(span, graph, scanPath);
|
|
4745
4878
|
if (!ev) return;
|
|
4746
|
-
await
|
|
4747
|
-
await
|
|
4879
|
+
await fs8.mkdir(path9.dirname(errorsPath), { recursive: true });
|
|
4880
|
+
await fs8.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
4748
4881
|
emitIncidentEvent(project, ev);
|
|
4749
4882
|
};
|
|
4750
4883
|
}
|
|
@@ -5247,13 +5380,13 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
5247
5380
|
return { count: events.length, events };
|
|
5248
5381
|
}
|
|
5249
5382
|
async function appendStaleEvents(staleEventsPath, events) {
|
|
5250
|
-
await
|
|
5383
|
+
await fs8.mkdir(path9.dirname(staleEventsPath), { recursive: true });
|
|
5251
5384
|
const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
5252
|
-
await
|
|
5385
|
+
await fs8.appendFile(staleEventsPath, lines, "utf8");
|
|
5253
5386
|
}
|
|
5254
5387
|
async function readStaleEvents(staleEventsPath) {
|
|
5255
5388
|
try {
|
|
5256
|
-
const raw = await
|
|
5389
|
+
const raw = await fs8.readFile(staleEventsPath, "utf8");
|
|
5257
5390
|
return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
5258
5391
|
} catch (err) {
|
|
5259
5392
|
if (err.code === "ENOENT") return [];
|
|
@@ -5289,7 +5422,7 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
5289
5422
|
var INCIDENT_READ_MAX_BYTES = 32 * 1024 * 1024;
|
|
5290
5423
|
var INCIDENT_READ_MAX_EVENTS = 5e3;
|
|
5291
5424
|
async function readErrorFileTail(errorsPath, maxBytes) {
|
|
5292
|
-
const handle = await
|
|
5425
|
+
const handle = await fs8.open(errorsPath, "r");
|
|
5293
5426
|
try {
|
|
5294
5427
|
const { size } = await handle.stat();
|
|
5295
5428
|
if (size <= maxBytes) {
|
|
@@ -5546,19 +5679,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
5546
5679
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
5547
5680
|
let best = { path: [start], edges: [] };
|
|
5548
5681
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
5549
|
-
function step(node,
|
|
5550
|
-
if (
|
|
5551
|
-
best = { path: [...
|
|
5682
|
+
function step(node, path74, edges) {
|
|
5683
|
+
if (path74.length > best.path.length) {
|
|
5684
|
+
best = { path: [...path74], edges: [...edges] };
|
|
5552
5685
|
}
|
|
5553
|
-
if (
|
|
5686
|
+
if (path74.length - 1 >= maxDepth) return;
|
|
5554
5687
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
5555
5688
|
for (const [srcId, edge] of incoming) {
|
|
5556
5689
|
if (visited.has(srcId)) continue;
|
|
5557
5690
|
visited.add(srcId);
|
|
5558
|
-
|
|
5691
|
+
path74.push(srcId);
|
|
5559
5692
|
edges.push(edge);
|
|
5560
|
-
step(srcId,
|
|
5561
|
-
|
|
5693
|
+
step(srcId, path74, edges);
|
|
5694
|
+
path74.pop();
|
|
5562
5695
|
edges.pop();
|
|
5563
5696
|
visited.delete(srcId);
|
|
5564
5697
|
}
|
|
@@ -5773,20 +5906,20 @@ function dominantFailingCall(graph, serviceId17, visited) {
|
|
|
5773
5906
|
return best;
|
|
5774
5907
|
}
|
|
5775
5908
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
5776
|
-
const
|
|
5909
|
+
const path74 = [originServiceId];
|
|
5777
5910
|
const edges = [];
|
|
5778
5911
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
5779
5912
|
let current = originServiceId;
|
|
5780
5913
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
5781
5914
|
const hop = dominantFailingCall(graph, current, visited);
|
|
5782
5915
|
if (!hop) break;
|
|
5783
|
-
|
|
5916
|
+
path74.push(hop.nextService);
|
|
5784
5917
|
edges.push(hop.edge);
|
|
5785
5918
|
visited.add(hop.nextService);
|
|
5786
5919
|
current = hop.nextService;
|
|
5787
5920
|
}
|
|
5788
5921
|
if (edges.length === 0) return null;
|
|
5789
|
-
return { path:
|
|
5922
|
+
return { path: path74, edges, culprit: current };
|
|
5790
5923
|
}
|
|
5791
5924
|
function isStaleCallEdge(e) {
|
|
5792
5925
|
return e.type === EdgeType6.CALLS && e.provenance === Provenance6.STALE;
|
|
@@ -5822,26 +5955,26 @@ function dominantStaleCall(graph, serviceId17, visited) {
|
|
|
5822
5955
|
return best;
|
|
5823
5956
|
}
|
|
5824
5957
|
function followStaleCallChain(graph, originServiceId, maxDepth) {
|
|
5825
|
-
const
|
|
5958
|
+
const path74 = [originServiceId];
|
|
5826
5959
|
const edges = [];
|
|
5827
5960
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
5828
5961
|
let current = originServiceId;
|
|
5829
5962
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
5830
5963
|
const hop = dominantStaleCall(graph, current, visited);
|
|
5831
5964
|
if (!hop) break;
|
|
5832
|
-
|
|
5965
|
+
path74.push(hop.nextService);
|
|
5833
5966
|
edges.push(hop.edge);
|
|
5834
5967
|
visited.add(hop.nextService);
|
|
5835
5968
|
current = hop.nextService;
|
|
5836
5969
|
}
|
|
5837
5970
|
if (edges.length === 0) return null;
|
|
5838
|
-
return { path:
|
|
5971
|
+
return { path: path74, edges, culprit: current };
|
|
5839
5972
|
}
|
|
5840
5973
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
5841
5974
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
5842
5975
|
if (!chain) return null;
|
|
5843
5976
|
const culprit = chain.culprit;
|
|
5844
|
-
const
|
|
5977
|
+
const path74 = [...chain.path];
|
|
5845
5978
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
5846
5979
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
5847
5980
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -5849,14 +5982,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5849
5982
|
if (loc) {
|
|
5850
5983
|
let rootCauseNode = culprit;
|
|
5851
5984
|
if (loc.fileNode) {
|
|
5852
|
-
|
|
5985
|
+
path74.push(loc.fileNode);
|
|
5853
5986
|
edgeProvenances.push(Provenance6.OBSERVED);
|
|
5854
5987
|
rootCauseNode = loc.fileNode;
|
|
5855
5988
|
}
|
|
5856
5989
|
return RootCauseResultSchema.parse({
|
|
5857
5990
|
rootCauseNode,
|
|
5858
5991
|
rootCauseReason: loc.rootCauseReason,
|
|
5859
|
-
traversalPath:
|
|
5992
|
+
traversalPath: path74,
|
|
5860
5993
|
edgeProvenances,
|
|
5861
5994
|
confidence,
|
|
5862
5995
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -5868,7 +6001,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5868
6001
|
return RootCauseResultSchema.parse({
|
|
5869
6002
|
rootCauseNode: culprit,
|
|
5870
6003
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
5871
|
-
traversalPath:
|
|
6004
|
+
traversalPath: path74,
|
|
5872
6005
|
edgeProvenances,
|
|
5873
6006
|
confidence,
|
|
5874
6007
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -6521,10 +6654,10 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
|
6521
6654
|
traversalPath = staleChain.path;
|
|
6522
6655
|
edgeProvenances = staleChain.edges.map((e) => e.provenance);
|
|
6523
6656
|
} else if (top.node !== seedNode) {
|
|
6524
|
-
const
|
|
6525
|
-
if (
|
|
6526
|
-
traversalPath =
|
|
6527
|
-
edgeProvenances =
|
|
6657
|
+
const path74 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
|
|
6658
|
+
if (path74) {
|
|
6659
|
+
traversalPath = path74.nodes;
|
|
6660
|
+
edgeProvenances = path74.edges.map((e) => e.provenance);
|
|
6528
6661
|
} else {
|
|
6529
6662
|
traversalPath = [errorNodeId, top.node];
|
|
6530
6663
|
edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
|
|
@@ -6559,15 +6692,15 @@ function fixRecommendationForTop(top, seedNode, legacy) {
|
|
|
6559
6692
|
}
|
|
6560
6693
|
|
|
6561
6694
|
// src/extract/services.ts
|
|
6562
|
-
import { promises as
|
|
6563
|
-
import
|
|
6564
|
-
import
|
|
6695
|
+
import { promises as fs19 } from "fs";
|
|
6696
|
+
import path21 from "path";
|
|
6697
|
+
import ignore2 from "ignore";
|
|
6565
6698
|
import { minimatch as minimatch2 } from "minimatch";
|
|
6566
6699
|
import { NodeType as NodeType15, serviceId as serviceId11 } from "@neat.is/types";
|
|
6567
6700
|
|
|
6568
6701
|
// src/extract/python.ts
|
|
6569
|
-
import { promises as
|
|
6570
|
-
import
|
|
6702
|
+
import { promises as fs9 } from "fs";
|
|
6703
|
+
import path10 from "path";
|
|
6571
6704
|
import { parse as parseToml } from "smol-toml";
|
|
6572
6705
|
var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
|
|
6573
6706
|
function parseRequirementsTxt(content) {
|
|
@@ -6600,25 +6733,25 @@ function depsFromPyProject(pyproject) {
|
|
|
6600
6733
|
return out;
|
|
6601
6734
|
}
|
|
6602
6735
|
async function discoverPythonService(serviceDir) {
|
|
6603
|
-
const pyprojectPath =
|
|
6604
|
-
const requirementsPath =
|
|
6605
|
-
const setupPath =
|
|
6736
|
+
const pyprojectPath = path10.join(serviceDir, "pyproject.toml");
|
|
6737
|
+
const requirementsPath = path10.join(serviceDir, "requirements.txt");
|
|
6738
|
+
const setupPath = path10.join(serviceDir, "setup.py");
|
|
6606
6739
|
const hasPyproject = await exists(pyprojectPath);
|
|
6607
6740
|
const hasRequirements = await exists(requirementsPath);
|
|
6608
6741
|
const hasSetup = await exists(setupPath);
|
|
6609
6742
|
if (!hasPyproject && !hasRequirements && !hasSetup) return null;
|
|
6610
|
-
let name =
|
|
6743
|
+
let name = path10.basename(serviceDir);
|
|
6611
6744
|
let version;
|
|
6612
6745
|
const dependencies = {};
|
|
6613
6746
|
if (hasPyproject) {
|
|
6614
|
-
const raw = await
|
|
6747
|
+
const raw = await fs9.readFile(pyprojectPath, "utf8");
|
|
6615
6748
|
const pyproject = parseToml(raw);
|
|
6616
6749
|
name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
|
|
6617
6750
|
version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
|
|
6618
6751
|
Object.assign(dependencies, depsFromPyProject(pyproject));
|
|
6619
6752
|
}
|
|
6620
6753
|
if (hasRequirements) {
|
|
6621
|
-
const raw = await
|
|
6754
|
+
const raw = await fs9.readFile(requirementsPath, "utf8");
|
|
6622
6755
|
Object.assign(dependencies, parseRequirementsTxt(raw));
|
|
6623
6756
|
}
|
|
6624
6757
|
return { name, version, dependencies };
|
|
@@ -6632,8 +6765,8 @@ function pythonToPackage(service) {
|
|
|
6632
6765
|
}
|
|
6633
6766
|
|
|
6634
6767
|
// src/extract/go.ts
|
|
6635
|
-
import { promises as
|
|
6636
|
-
import
|
|
6768
|
+
import { promises as fs10 } from "fs";
|
|
6769
|
+
import path11 from "path";
|
|
6637
6770
|
import { NodeType as NodeType6, serviceId as serviceId2 } from "@neat.is/types";
|
|
6638
6771
|
function parseGoMod(source) {
|
|
6639
6772
|
const module = source.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
|
|
@@ -6660,7 +6793,7 @@ function goFramework(deps) {
|
|
|
6660
6793
|
async function discoverGoService(scanPath, dir) {
|
|
6661
6794
|
let raw;
|
|
6662
6795
|
try {
|
|
6663
|
-
raw = await
|
|
6796
|
+
raw = await fs10.readFile(path11.join(dir, "go.mod"), "utf8");
|
|
6664
6797
|
} catch {
|
|
6665
6798
|
return null;
|
|
6666
6799
|
}
|
|
@@ -6675,15 +6808,15 @@ async function discoverGoService(scanPath, dir) {
|
|
|
6675
6808
|
name,
|
|
6676
6809
|
language: "go",
|
|
6677
6810
|
dependencies: mod.dependencies,
|
|
6678
|
-
repoPath:
|
|
6811
|
+
repoPath: path11.relative(scanPath, dir),
|
|
6679
6812
|
...framework ? { framework } : {}
|
|
6680
6813
|
};
|
|
6681
6814
|
return { pkg, dir, node };
|
|
6682
6815
|
}
|
|
6683
6816
|
|
|
6684
6817
|
// src/extract/ruby.ts
|
|
6685
|
-
import { promises as
|
|
6686
|
-
import
|
|
6818
|
+
import { promises as fs11 } from "fs";
|
|
6819
|
+
import path12 from "path";
|
|
6687
6820
|
import { NodeType as NodeType7, serviceId as serviceId3 } from "@neat.is/types";
|
|
6688
6821
|
function parseGemfile(source) {
|
|
6689
6822
|
const dependencies = {};
|
|
@@ -6700,12 +6833,12 @@ function parseGemfile(source) {
|
|
|
6700
6833
|
async function discoverRubyService(scanPath, dir) {
|
|
6701
6834
|
let raw;
|
|
6702
6835
|
try {
|
|
6703
|
-
raw = await
|
|
6836
|
+
raw = await fs11.readFile(path12.join(dir, "Gemfile"), "utf8");
|
|
6704
6837
|
} catch {
|
|
6705
6838
|
return null;
|
|
6706
6839
|
}
|
|
6707
6840
|
const gemfile = parseGemfile(raw);
|
|
6708
|
-
const name =
|
|
6841
|
+
const name = path12.basename(dir);
|
|
6709
6842
|
const pkg = { name, dependencies: gemfile.dependencies };
|
|
6710
6843
|
const node = {
|
|
6711
6844
|
id: serviceId3(name),
|
|
@@ -6713,14 +6846,14 @@ async function discoverRubyService(scanPath, dir) {
|
|
|
6713
6846
|
name,
|
|
6714
6847
|
language: "ruby",
|
|
6715
6848
|
dependencies: gemfile.dependencies,
|
|
6716
|
-
repoPath:
|
|
6849
|
+
repoPath: path12.relative(scanPath, dir),
|
|
6717
6850
|
...gemfile.dependencies["rails"] !== void 0 ? { framework: "rails" } : {}
|
|
6718
6851
|
};
|
|
6719
6852
|
return { pkg, dir, node };
|
|
6720
6853
|
}
|
|
6721
6854
|
|
|
6722
6855
|
// src/extract/php.ts
|
|
6723
|
-
import
|
|
6856
|
+
import path13 from "path";
|
|
6724
6857
|
import { NodeType as NodeType8, serviceId as serviceId4 } from "@neat.is/types";
|
|
6725
6858
|
function parseComposerJson(composer) {
|
|
6726
6859
|
return {
|
|
@@ -6733,12 +6866,12 @@ function parseComposerJson(composer) {
|
|
|
6733
6866
|
async function discoverPhpService(scanPath, dir) {
|
|
6734
6867
|
let composer;
|
|
6735
6868
|
try {
|
|
6736
|
-
composer = await readJson(
|
|
6869
|
+
composer = await readJson(path13.join(dir, "composer.json"));
|
|
6737
6870
|
} catch {
|
|
6738
6871
|
return null;
|
|
6739
6872
|
}
|
|
6740
6873
|
const { dependencies } = parseComposerJson(composer);
|
|
6741
|
-
const name =
|
|
6874
|
+
const name = path13.basename(dir);
|
|
6742
6875
|
const pkg = { name, dependencies };
|
|
6743
6876
|
const node = {
|
|
6744
6877
|
id: serviceId4(name),
|
|
@@ -6746,30 +6879,30 @@ async function discoverPhpService(scanPath, dir) {
|
|
|
6746
6879
|
name,
|
|
6747
6880
|
language: "php",
|
|
6748
6881
|
dependencies,
|
|
6749
|
-
repoPath:
|
|
6882
|
+
repoPath: path13.relative(scanPath, dir),
|
|
6750
6883
|
...dependencies["laravel/framework"] !== void 0 ? { framework: "laravel" } : {}
|
|
6751
6884
|
};
|
|
6752
6885
|
return { pkg, dir, node };
|
|
6753
6886
|
}
|
|
6754
6887
|
|
|
6755
6888
|
// src/extract/csharp.ts
|
|
6756
|
-
import { promises as
|
|
6757
|
-
import
|
|
6889
|
+
import { promises as fs12 } from "fs";
|
|
6890
|
+
import path14 from "path";
|
|
6758
6891
|
import { NodeType as NodeType9, serviceId as serviceId5 } from "@neat.is/types";
|
|
6759
6892
|
var CSHARP_PROJECT_EXTS = /* @__PURE__ */ new Set([".csproj", ".sln"]);
|
|
6760
6893
|
async function projectFiles(dir) {
|
|
6761
|
-
const entries = await
|
|
6894
|
+
const entries = await fs12.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
6762
6895
|
const out = [];
|
|
6763
6896
|
for (const entry of entries) {
|
|
6764
6897
|
if (!entry.isFile()) continue;
|
|
6765
|
-
if (CSHARP_PROJECT_EXTS.has(
|
|
6898
|
+
if (CSHARP_PROJECT_EXTS.has(path14.extname(entry.name).toLowerCase())) out.push(entry.name);
|
|
6766
6899
|
}
|
|
6767
6900
|
return out.sort();
|
|
6768
6901
|
}
|
|
6769
6902
|
var NESTED_PROJECT_DIR = "src";
|
|
6770
6903
|
async function resolveProjectDir(dir) {
|
|
6771
6904
|
if ((await projectFiles(dir)).length > 0) return dir;
|
|
6772
|
-
const nested =
|
|
6905
|
+
const nested = path14.join(dir, NESTED_PROJECT_DIR);
|
|
6773
6906
|
if ((await projectFiles(nested)).length > 0) return nested;
|
|
6774
6907
|
return null;
|
|
6775
6908
|
}
|
|
@@ -6794,10 +6927,10 @@ async function discoverCsharpService(scanPath, dir) {
|
|
|
6794
6927
|
if (projectDir === null) return null;
|
|
6795
6928
|
const projects = await projectFiles(projectDir);
|
|
6796
6929
|
const csproj = projects.find((p) => p.toLowerCase().endsWith(".csproj"));
|
|
6797
|
-
const name = csproj ?
|
|
6930
|
+
const name = csproj ? path14.basename(csproj, path14.extname(csproj)) : path14.basename(dir);
|
|
6798
6931
|
let dependencies = {};
|
|
6799
6932
|
if (csproj) {
|
|
6800
|
-
const raw = await
|
|
6933
|
+
const raw = await fs12.readFile(path14.join(projectDir, csproj), "utf8").catch(() => "");
|
|
6801
6934
|
dependencies = parseCsproj(raw).dependencies;
|
|
6802
6935
|
}
|
|
6803
6936
|
const pkg = { name, dependencies };
|
|
@@ -6809,19 +6942,19 @@ async function discoverCsharpService(scanPath, dir) {
|
|
|
6809
6942
|
dependencies,
|
|
6810
6943
|
// Anchor on the directory that holds the project and its `.cs` files, so file
|
|
6811
6944
|
// attribution reaches the source whether it sits at the root or under `src/`.
|
|
6812
|
-
repoPath:
|
|
6945
|
+
repoPath: path14.relative(scanPath, projectDir)
|
|
6813
6946
|
};
|
|
6814
6947
|
return { pkg, dir: projectDir, node };
|
|
6815
6948
|
}
|
|
6816
6949
|
|
|
6817
6950
|
// src/extract/java.ts
|
|
6818
|
-
import { promises as
|
|
6819
|
-
import
|
|
6951
|
+
import { promises as fs13 } from "fs";
|
|
6952
|
+
import path15 from "path";
|
|
6820
6953
|
import { NodeType as NodeType10, serviceId as serviceId6 } from "@neat.is/types";
|
|
6821
6954
|
var JAVA_MANIFESTS = ["pom.xml", "build.gradle", "build.gradle.kts"];
|
|
6822
6955
|
async function hasJavaManifest(dir) {
|
|
6823
6956
|
for (const manifest of JAVA_MANIFESTS) {
|
|
6824
|
-
if (await exists(
|
|
6957
|
+
if (await exists(path15.join(dir, manifest))) return true;
|
|
6825
6958
|
}
|
|
6826
6959
|
return false;
|
|
6827
6960
|
}
|
|
@@ -6856,19 +6989,19 @@ function parseGradle(source) {
|
|
|
6856
6989
|
return { dependencies };
|
|
6857
6990
|
}
|
|
6858
6991
|
async function discoverJavaService(scanPath, dir) {
|
|
6859
|
-
const pomPath =
|
|
6860
|
-
const gradlePath =
|
|
6861
|
-
const gradleKtsPath =
|
|
6992
|
+
const pomPath = path15.join(dir, "pom.xml");
|
|
6993
|
+
const gradlePath = path15.join(dir, "build.gradle");
|
|
6994
|
+
const gradleKtsPath = path15.join(dir, "build.gradle.kts");
|
|
6862
6995
|
let manifest = null;
|
|
6863
6996
|
if (await exists(pomPath)) {
|
|
6864
|
-
manifest = parsePomXml(await
|
|
6997
|
+
manifest = parsePomXml(await fs13.readFile(pomPath, "utf8").catch(() => ""));
|
|
6865
6998
|
} else if (await exists(gradlePath)) {
|
|
6866
|
-
manifest = parseGradle(await
|
|
6999
|
+
manifest = parseGradle(await fs13.readFile(gradlePath, "utf8").catch(() => ""));
|
|
6867
7000
|
} else if (await exists(gradleKtsPath)) {
|
|
6868
|
-
manifest = parseGradle(await
|
|
7001
|
+
manifest = parseGradle(await fs13.readFile(gradleKtsPath, "utf8").catch(() => ""));
|
|
6869
7002
|
}
|
|
6870
7003
|
if (!manifest) return null;
|
|
6871
|
-
const name = manifest.name?.trim() ||
|
|
7004
|
+
const name = manifest.name?.trim() || path15.basename(dir);
|
|
6872
7005
|
const dependencies = manifest.dependencies;
|
|
6873
7006
|
const pkg = { name, dependencies };
|
|
6874
7007
|
const node = {
|
|
@@ -6877,14 +7010,14 @@ async function discoverJavaService(scanPath, dir) {
|
|
|
6877
7010
|
name,
|
|
6878
7011
|
language: "java",
|
|
6879
7012
|
dependencies,
|
|
6880
|
-
repoPath:
|
|
7013
|
+
repoPath: path15.relative(scanPath, dir)
|
|
6881
7014
|
};
|
|
6882
7015
|
return { pkg, dir, node };
|
|
6883
7016
|
}
|
|
6884
7017
|
|
|
6885
7018
|
// src/extract/kotlin.ts
|
|
6886
|
-
import { promises as
|
|
6887
|
-
import
|
|
7019
|
+
import { promises as fs14 } from "fs";
|
|
7020
|
+
import path16 from "path";
|
|
6888
7021
|
import { NodeType as NodeType11, serviceId as serviceId7 } from "@neat.is/types";
|
|
6889
7022
|
var KOTLIN_SOURCE_EXT = ".kt";
|
|
6890
7023
|
var JAVA_SOURCE_EXT = ".java";
|
|
@@ -6894,15 +7027,15 @@ async function dominantJvmSourceLanguage(dir) {
|
|
|
6894
7027
|
let java = 0;
|
|
6895
7028
|
async function recurse(current, depth) {
|
|
6896
7029
|
if (depth > SOURCE_PROBE_MAX_DEPTH) return;
|
|
6897
|
-
const entries = await
|
|
7030
|
+
const entries = await fs14.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
6898
7031
|
for (const entry of entries) {
|
|
6899
7032
|
if (entry.isDirectory()) {
|
|
6900
7033
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6901
|
-
await recurse(
|
|
7034
|
+
await recurse(path16.join(current, entry.name), depth + 1);
|
|
6902
7035
|
continue;
|
|
6903
7036
|
}
|
|
6904
7037
|
if (!entry.isFile()) continue;
|
|
6905
|
-
const ext =
|
|
7038
|
+
const ext = path16.extname(entry.name).toLowerCase();
|
|
6906
7039
|
if (ext === KOTLIN_SOURCE_EXT) kt++;
|
|
6907
7040
|
else if (ext === JAVA_SOURCE_EXT) java++;
|
|
6908
7041
|
}
|
|
@@ -6913,17 +7046,17 @@ async function dominantJvmSourceLanguage(dir) {
|
|
|
6913
7046
|
return void 0;
|
|
6914
7047
|
}
|
|
6915
7048
|
async function readJvmManifest(dir) {
|
|
6916
|
-
const pomPath =
|
|
6917
|
-
const gradlePath =
|
|
6918
|
-
const gradleKtsPath =
|
|
7049
|
+
const pomPath = path16.join(dir, JAVA_MANIFESTS[0]);
|
|
7050
|
+
const gradlePath = path16.join(dir, JAVA_MANIFESTS[1]);
|
|
7051
|
+
const gradleKtsPath = path16.join(dir, JAVA_MANIFESTS[2]);
|
|
6919
7052
|
if (await exists(pomPath)) {
|
|
6920
|
-
return parsePomXml(await
|
|
7053
|
+
return parsePomXml(await fs14.readFile(pomPath, "utf8").catch(() => ""));
|
|
6921
7054
|
}
|
|
6922
7055
|
if (await exists(gradlePath)) {
|
|
6923
|
-
return parseGradle(await
|
|
7056
|
+
return parseGradle(await fs14.readFile(gradlePath, "utf8").catch(() => ""));
|
|
6924
7057
|
}
|
|
6925
7058
|
if (await exists(gradleKtsPath)) {
|
|
6926
|
-
return parseGradle(await
|
|
7059
|
+
return parseGradle(await fs14.readFile(gradleKtsPath, "utf8").catch(() => ""));
|
|
6927
7060
|
}
|
|
6928
7061
|
return null;
|
|
6929
7062
|
}
|
|
@@ -6932,7 +7065,7 @@ async function discoverKotlinService(scanPath, dir) {
|
|
|
6932
7065
|
if (await dominantJvmSourceLanguage(dir) !== "kotlin") return null;
|
|
6933
7066
|
const manifest = await readJvmManifest(dir);
|
|
6934
7067
|
if (!manifest) return null;
|
|
6935
|
-
const name = manifest.name?.trim() ||
|
|
7068
|
+
const name = manifest.name?.trim() || path16.basename(dir);
|
|
6936
7069
|
const dependencies = manifest.dependencies;
|
|
6937
7070
|
const pkg = { name, dependencies };
|
|
6938
7071
|
const node = {
|
|
@@ -6941,19 +7074,19 @@ async function discoverKotlinService(scanPath, dir) {
|
|
|
6941
7074
|
name,
|
|
6942
7075
|
language: "kotlin",
|
|
6943
7076
|
dependencies,
|
|
6944
|
-
repoPath:
|
|
7077
|
+
repoPath: path16.relative(scanPath, dir)
|
|
6945
7078
|
};
|
|
6946
7079
|
return { pkg, dir, node };
|
|
6947
7080
|
}
|
|
6948
7081
|
|
|
6949
7082
|
// src/extract/rust.ts
|
|
6950
|
-
import { promises as
|
|
6951
|
-
import
|
|
7083
|
+
import { promises as fs15 } from "fs";
|
|
7084
|
+
import path17 from "path";
|
|
6952
7085
|
import { parse as parseToml2 } from "smol-toml";
|
|
6953
7086
|
import { NodeType as NodeType12, serviceId as serviceId8 } from "@neat.is/types";
|
|
6954
7087
|
var CARGO_MANIFEST = "Cargo.toml";
|
|
6955
7088
|
async function hasRustManifest(dir) {
|
|
6956
|
-
return exists(
|
|
7089
|
+
return exists(path17.join(dir, CARGO_MANIFEST));
|
|
6957
7090
|
}
|
|
6958
7091
|
function parseCargoToml(source) {
|
|
6959
7092
|
const dependencies = {};
|
|
@@ -6978,10 +7111,10 @@ function parseCargoToml(source) {
|
|
|
6978
7111
|
return { name, dependencies };
|
|
6979
7112
|
}
|
|
6980
7113
|
async function discoverRustService(scanPath, dir) {
|
|
6981
|
-
const cargoPath =
|
|
7114
|
+
const cargoPath = path17.join(dir, CARGO_MANIFEST);
|
|
6982
7115
|
if (!await exists(cargoPath)) return null;
|
|
6983
|
-
const manifest = parseCargoToml(await
|
|
6984
|
-
const name = manifest.name?.trim() ||
|
|
7116
|
+
const manifest = parseCargoToml(await fs15.readFile(cargoPath, "utf8").catch(() => ""));
|
|
7117
|
+
const name = manifest.name?.trim() || path17.basename(dir);
|
|
6985
7118
|
const dependencies = manifest.dependencies;
|
|
6986
7119
|
const pkg = { name, dependencies };
|
|
6987
7120
|
const node = {
|
|
@@ -6990,18 +7123,18 @@ async function discoverRustService(scanPath, dir) {
|
|
|
6990
7123
|
name,
|
|
6991
7124
|
language: "rust",
|
|
6992
7125
|
dependencies,
|
|
6993
|
-
repoPath:
|
|
7126
|
+
repoPath: path17.relative(scanPath, dir)
|
|
6994
7127
|
};
|
|
6995
7128
|
return { pkg, dir, node };
|
|
6996
7129
|
}
|
|
6997
7130
|
|
|
6998
7131
|
// src/extract/cpp.ts
|
|
6999
|
-
import { promises as
|
|
7000
|
-
import
|
|
7132
|
+
import { promises as fs16 } from "fs";
|
|
7133
|
+
import path18 from "path";
|
|
7001
7134
|
import { NodeType as NodeType13, serviceId as serviceId9 } from "@neat.is/types";
|
|
7002
7135
|
var CMAKE_MANIFEST = "CMakeLists.txt";
|
|
7003
7136
|
async function hasCppManifest(dir) {
|
|
7004
|
-
return exists(
|
|
7137
|
+
return exists(path18.join(dir, CMAKE_MANIFEST));
|
|
7005
7138
|
}
|
|
7006
7139
|
function parseCMakeProjectName(source) {
|
|
7007
7140
|
const withoutComments = source.replace(/#.*$/gm, "");
|
|
@@ -7009,10 +7142,10 @@ function parseCMakeProjectName(source) {
|
|
|
7009
7142
|
return m?.[1];
|
|
7010
7143
|
}
|
|
7011
7144
|
async function discoverCppService(scanPath, dir) {
|
|
7012
|
-
const cmakePath =
|
|
7145
|
+
const cmakePath = path18.join(dir, CMAKE_MANIFEST);
|
|
7013
7146
|
if (!await exists(cmakePath)) return null;
|
|
7014
|
-
const source = await
|
|
7015
|
-
const name = parseCMakeProjectName(source)?.trim() ||
|
|
7147
|
+
const source = await fs16.readFile(cmakePath, "utf8").catch(() => "");
|
|
7148
|
+
const name = parseCMakeProjectName(source)?.trim() || path18.basename(dir);
|
|
7016
7149
|
const dependencies = {};
|
|
7017
7150
|
const pkg = { name, dependencies };
|
|
7018
7151
|
const node = {
|
|
@@ -7021,14 +7154,14 @@ async function discoverCppService(scanPath, dir) {
|
|
|
7021
7154
|
name,
|
|
7022
7155
|
language: "cpp",
|
|
7023
7156
|
dependencies,
|
|
7024
|
-
repoPath:
|
|
7157
|
+
repoPath: path18.relative(scanPath, dir)
|
|
7025
7158
|
};
|
|
7026
7159
|
return { pkg, dir, node };
|
|
7027
7160
|
}
|
|
7028
7161
|
|
|
7029
7162
|
// src/extract/dockerfile-service.ts
|
|
7030
|
-
import { promises as
|
|
7031
|
-
import
|
|
7163
|
+
import { promises as fs17 } from "fs";
|
|
7164
|
+
import path19 from "path";
|
|
7032
7165
|
import { NodeType as NodeType14, serviceId as serviceId10 } from "@neat.is/types";
|
|
7033
7166
|
function languageForSourceExt(ext) {
|
|
7034
7167
|
switch (ext) {
|
|
@@ -7070,12 +7203,12 @@ function languageForSourceExt(ext) {
|
|
|
7070
7203
|
}
|
|
7071
7204
|
var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java", "kotlin", "rust", "cpp"];
|
|
7072
7205
|
async function inferServiceLanguage(dir) {
|
|
7073
|
-
const entries = await
|
|
7206
|
+
const entries = await fs17.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
7074
7207
|
const counts = /* @__PURE__ */ new Map();
|
|
7075
7208
|
for (const entry of entries) {
|
|
7076
7209
|
if (!entry.isFile()) continue;
|
|
7077
7210
|
if (isNeatAuthoredSourceFile(entry.name)) continue;
|
|
7078
|
-
const ext =
|
|
7211
|
+
const ext = path19.extname(entry.name).toLowerCase();
|
|
7079
7212
|
if (!SERVICE_FILE_EXTENSIONS.has(ext)) continue;
|
|
7080
7213
|
const lang = languageForSourceExt(ext);
|
|
7081
7214
|
if (!lang) continue;
|
|
@@ -7094,7 +7227,7 @@ async function inferServiceLanguage(dir) {
|
|
|
7094
7227
|
return best;
|
|
7095
7228
|
}
|
|
7096
7229
|
async function hasLanguageManifest(dir) {
|
|
7097
|
-
return await exists(
|
|
7230
|
+
return await exists(path19.join(dir, "package.json")) || await exists(path19.join(dir, "pyproject.toml")) || await exists(path19.join(dir, "requirements.txt")) || await exists(path19.join(dir, "setup.py")) || await exists(path19.join(dir, "go.mod")) || await exists(path19.join(dir, "Gemfile")) || await exists(path19.join(dir, "composer.json")) || // C#'s marker is a glob (`*.csproj` / `*.sln`), so it reads the directory
|
|
7098
7231
|
// rather than probing a fixed filename (ADR-196).
|
|
7099
7232
|
await hasCsharpProject(dir) || // Java (and its JVM sibling Kotlin, ADR-199) key on the same Maven / Gradle
|
|
7100
7233
|
// build manifest (ADR-197), so this one check stands the Dockerfile fallback
|
|
@@ -7104,15 +7237,15 @@ async function hasLanguageManifest(dir) {
|
|
|
7104
7237
|
await hasCppManifest(dir);
|
|
7105
7238
|
}
|
|
7106
7239
|
async function hasDockerfileDeclaredService(dir) {
|
|
7107
|
-
if (!await exists(
|
|
7240
|
+
if (!await exists(path19.join(dir, "Dockerfile"))) return false;
|
|
7108
7241
|
return await inferServiceLanguage(dir) !== null;
|
|
7109
7242
|
}
|
|
7110
7243
|
async function discoverDockerfileService(scanPath, dir) {
|
|
7111
|
-
if (!await exists(
|
|
7244
|
+
if (!await exists(path19.join(dir, "Dockerfile"))) return null;
|
|
7112
7245
|
if (await hasLanguageManifest(dir)) return null;
|
|
7113
7246
|
const language = await inferServiceLanguage(dir);
|
|
7114
7247
|
if (!language) return null;
|
|
7115
|
-
const name =
|
|
7248
|
+
const name = path19.basename(dir);
|
|
7116
7249
|
const pkg = { name, dependencies: {} };
|
|
7117
7250
|
const node = {
|
|
7118
7251
|
id: serviceId10(name),
|
|
@@ -7120,23 +7253,23 @@ async function discoverDockerfileService(scanPath, dir) {
|
|
|
7120
7253
|
name,
|
|
7121
7254
|
language,
|
|
7122
7255
|
dependencies: {},
|
|
7123
|
-
repoPath:
|
|
7256
|
+
repoPath: path19.relative(scanPath, dir)
|
|
7124
7257
|
};
|
|
7125
7258
|
return { pkg, dir, node };
|
|
7126
7259
|
}
|
|
7127
7260
|
|
|
7128
7261
|
// src/extract/owners.ts
|
|
7129
|
-
import { promises as
|
|
7130
|
-
import
|
|
7262
|
+
import { promises as fs18 } from "fs";
|
|
7263
|
+
import path20 from "path";
|
|
7131
7264
|
import { minimatch } from "minimatch";
|
|
7132
7265
|
async function loadCodeowners(scanPath) {
|
|
7133
7266
|
const candidates = [
|
|
7134
|
-
|
|
7135
|
-
|
|
7267
|
+
path20.join(scanPath, "CODEOWNERS"),
|
|
7268
|
+
path20.join(scanPath, ".github", "CODEOWNERS")
|
|
7136
7269
|
];
|
|
7137
7270
|
for (const file of candidates) {
|
|
7138
7271
|
if (await exists(file)) {
|
|
7139
|
-
const raw = await
|
|
7272
|
+
const raw = await fs18.readFile(file, "utf8");
|
|
7140
7273
|
return parseCodeowners(raw);
|
|
7141
7274
|
}
|
|
7142
7275
|
}
|
|
@@ -7154,7 +7287,7 @@ function parseCodeowners(raw) {
|
|
|
7154
7287
|
return { rules };
|
|
7155
7288
|
}
|
|
7156
7289
|
function matchOwner(file, repoPath) {
|
|
7157
|
-
const normalized = repoPath.split(
|
|
7290
|
+
const normalized = repoPath.split(path20.sep).join("/");
|
|
7158
7291
|
for (const rule of file.rules) {
|
|
7159
7292
|
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
7160
7293
|
}
|
|
@@ -7170,7 +7303,7 @@ function matchesPattern(rawPattern, repoPath) {
|
|
|
7170
7303
|
return false;
|
|
7171
7304
|
}
|
|
7172
7305
|
async function readPackageJsonAuthor(serviceDir) {
|
|
7173
|
-
const pkgPath =
|
|
7306
|
+
const pkgPath = path20.join(serviceDir, "package.json");
|
|
7174
7307
|
if (!await exists(pkgPath)) return null;
|
|
7175
7308
|
try {
|
|
7176
7309
|
const pkg = await readJson(pkgPath);
|
|
@@ -7207,36 +7340,36 @@ function workspaceGlobs(pkg) {
|
|
|
7207
7340
|
return null;
|
|
7208
7341
|
}
|
|
7209
7342
|
async function hasPythonManifest(dir) {
|
|
7210
|
-
return await exists(
|
|
7343
|
+
return await exists(path21.join(dir, "pyproject.toml")) || await exists(path21.join(dir, "requirements.txt")) || await exists(path21.join(dir, "setup.py"));
|
|
7211
7344
|
}
|
|
7212
7345
|
async function hasGoManifest(dir) {
|
|
7213
|
-
return exists(
|
|
7346
|
+
return exists(path21.join(dir, "go.mod"));
|
|
7214
7347
|
}
|
|
7215
7348
|
async function hasRubyManifest(dir) {
|
|
7216
|
-
return exists(
|
|
7349
|
+
return exists(path21.join(dir, "Gemfile"));
|
|
7217
7350
|
}
|
|
7218
7351
|
async function hasPhpManifest(dir) {
|
|
7219
|
-
return exists(
|
|
7352
|
+
return exists(path21.join(dir, "composer.json"));
|
|
7220
7353
|
}
|
|
7221
7354
|
async function hasCsharpManifest(dir) {
|
|
7222
7355
|
return hasCsharpProject(dir);
|
|
7223
7356
|
}
|
|
7224
7357
|
async function loadGitignore(scanPath) {
|
|
7225
|
-
const gitignorePath =
|
|
7358
|
+
const gitignorePath = path21.join(scanPath, ".gitignore");
|
|
7226
7359
|
if (!await exists(gitignorePath)) return null;
|
|
7227
|
-
const raw = await
|
|
7228
|
-
return
|
|
7360
|
+
const raw = await fs19.readFile(gitignorePath, "utf8");
|
|
7361
|
+
return ignore2().add(raw);
|
|
7229
7362
|
}
|
|
7230
7363
|
async function walkDirs(start, scanPath, options, visit) {
|
|
7231
7364
|
async function recurse(current, depth) {
|
|
7232
7365
|
if (depth > options.maxDepth) return;
|
|
7233
|
-
const entries = await
|
|
7366
|
+
const entries = await fs19.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
7234
7367
|
for (const entry of entries) {
|
|
7235
7368
|
if (!entry.isDirectory()) continue;
|
|
7236
7369
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
7237
|
-
const child =
|
|
7370
|
+
const child = path21.join(current, entry.name);
|
|
7238
7371
|
if (options.ig) {
|
|
7239
|
-
const rel =
|
|
7372
|
+
const rel = path21.relative(scanPath, child).split(path21.sep).join("/");
|
|
7240
7373
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
7241
7374
|
}
|
|
7242
7375
|
if (await isPythonVenvDir(child)) continue;
|
|
@@ -7252,8 +7385,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
7252
7385
|
for (const raw of globs) {
|
|
7253
7386
|
const pattern = raw.replace(/^\.\//, "");
|
|
7254
7387
|
if (!pattern.includes("*")) {
|
|
7255
|
-
const candidate =
|
|
7256
|
-
if (await exists(
|
|
7388
|
+
const candidate = path21.join(scanPath, pattern);
|
|
7389
|
+
if (await exists(path21.join(candidate, "package.json"))) found.add(candidate);
|
|
7257
7390
|
continue;
|
|
7258
7391
|
}
|
|
7259
7392
|
const segments = pattern.split("/");
|
|
@@ -7262,13 +7395,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
7262
7395
|
if (seg.includes("*")) break;
|
|
7263
7396
|
staticSegments.push(seg);
|
|
7264
7397
|
}
|
|
7265
|
-
const start =
|
|
7398
|
+
const start = path21.join(scanPath, ...staticSegments);
|
|
7266
7399
|
if (!await exists(start)) continue;
|
|
7267
7400
|
const hasDoubleStar = pattern.includes("**");
|
|
7268
7401
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
7269
7402
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
7270
|
-
const rel =
|
|
7271
|
-
if (minimatch2(rel, pattern) && await exists(
|
|
7403
|
+
const rel = path21.relative(scanPath, dir).split(path21.sep).join("/");
|
|
7404
|
+
if (minimatch2(rel, pattern) && await exists(path21.join(dir, "package.json"))) {
|
|
7272
7405
|
found.add(dir);
|
|
7273
7406
|
}
|
|
7274
7407
|
});
|
|
@@ -7291,18 +7424,18 @@ function detectJsFramework(pkg) {
|
|
|
7291
7424
|
async function detectJsServiceLanguage(dir, pkg) {
|
|
7292
7425
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
7293
7426
|
if (deps["typescript"] !== void 0) return "typescript";
|
|
7294
|
-
const entries = await
|
|
7427
|
+
const entries = await fs19.readdir(dir).catch(() => []);
|
|
7295
7428
|
if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
|
|
7296
7429
|
return "javascript";
|
|
7297
7430
|
}
|
|
7298
7431
|
async function discoverNodeService(scanPath, dir) {
|
|
7299
|
-
const pkgPath =
|
|
7432
|
+
const pkgPath = path21.join(dir, "package.json");
|
|
7300
7433
|
if (!await exists(pkgPath)) return null;
|
|
7301
7434
|
let pkg;
|
|
7302
7435
|
try {
|
|
7303
7436
|
pkg = await readJson(pkgPath);
|
|
7304
7437
|
} catch (err) {
|
|
7305
|
-
recordExtractionError("services",
|
|
7438
|
+
recordExtractionError("services", path21.relative(scanPath, pkgPath), err);
|
|
7306
7439
|
return null;
|
|
7307
7440
|
}
|
|
7308
7441
|
if (!pkg.name) return null;
|
|
@@ -7315,7 +7448,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
7315
7448
|
language,
|
|
7316
7449
|
version: pkg.version,
|
|
7317
7450
|
dependencies: pkg.dependencies ?? {},
|
|
7318
|
-
repoPath:
|
|
7451
|
+
repoPath: path21.relative(scanPath, dir),
|
|
7319
7452
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
|
|
7320
7453
|
...framework ? { framework } : {}
|
|
7321
7454
|
};
|
|
@@ -7332,12 +7465,12 @@ async function discoverPyService(scanPath, dir) {
|
|
|
7332
7465
|
language: "python",
|
|
7333
7466
|
version: py.version,
|
|
7334
7467
|
dependencies: py.dependencies,
|
|
7335
|
-
repoPath:
|
|
7468
|
+
repoPath: path21.relative(scanPath, dir)
|
|
7336
7469
|
};
|
|
7337
7470
|
return { pkg, dir, node };
|
|
7338
7471
|
}
|
|
7339
7472
|
async function discoverServices(scanPath) {
|
|
7340
|
-
const rootPkgPath =
|
|
7473
|
+
const rootPkgPath = path21.join(scanPath, "package.json");
|
|
7341
7474
|
let rootPkg = null;
|
|
7342
7475
|
if (await exists(rootPkgPath)) {
|
|
7343
7476
|
try {
|
|
@@ -7345,7 +7478,7 @@ async function discoverServices(scanPath) {
|
|
|
7345
7478
|
} catch (err) {
|
|
7346
7479
|
recordExtractionError(
|
|
7347
7480
|
"services workspaces",
|
|
7348
|
-
|
|
7481
|
+
path21.relative(scanPath, rootPkgPath),
|
|
7349
7482
|
err
|
|
7350
7483
|
);
|
|
7351
7484
|
}
|
|
@@ -7366,7 +7499,7 @@ async function discoverServices(scanPath) {
|
|
|
7366
7499
|
scanPath,
|
|
7367
7500
|
{ maxDepth: parseScanDepth(), ig },
|
|
7368
7501
|
async (dir) => {
|
|
7369
|
-
if (await exists(
|
|
7502
|
+
if (await exists(path21.join(dir, "package.json"))) {
|
|
7370
7503
|
candidateDirs.push(dir);
|
|
7371
7504
|
} else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir) || await hasCsharpManifest(dir) || await hasJavaManifest(dir) || await hasRustManifest(dir) || await hasCppManifest(dir)) {
|
|
7372
7505
|
candidateDirs.push(dir);
|
|
@@ -7394,8 +7527,8 @@ async function discoverServices(scanPath) {
|
|
|
7394
7527
|
seenDirs.add(service.dir);
|
|
7395
7528
|
const existingDir = seen.get(service.node.name);
|
|
7396
7529
|
if (existingDir !== void 0) {
|
|
7397
|
-
const a =
|
|
7398
|
-
const b =
|
|
7530
|
+
const a = path21.relative(scanPath, existingDir) || ".";
|
|
7531
|
+
const b = path21.relative(scanPath, dir) || ".";
|
|
7399
7532
|
console.warn(
|
|
7400
7533
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
7401
7534
|
);
|
|
@@ -7409,14 +7542,14 @@ async function discoverServices(scanPath) {
|
|
|
7409
7542
|
const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
|
|
7410
7543
|
if (owner !== void 0) service.node.owner = owner;
|
|
7411
7544
|
}
|
|
7412
|
-
const resolvedDirs = out.map((s) =>
|
|
7545
|
+
const resolvedDirs = out.map((s) => path21.resolve(s.dir));
|
|
7413
7546
|
for (let i = 0; i < out.length; i++) {
|
|
7414
7547
|
const self = resolvedDirs[i];
|
|
7415
7548
|
const nested = [];
|
|
7416
7549
|
for (let j = 0; j < out.length; j++) {
|
|
7417
7550
|
if (i === j) continue;
|
|
7418
7551
|
const other = resolvedDirs[j];
|
|
7419
|
-
if (other.startsWith(self +
|
|
7552
|
+
if (other.startsWith(self + path21.sep)) nested.push(other);
|
|
7420
7553
|
}
|
|
7421
7554
|
out[i].excludeDirs = nested;
|
|
7422
7555
|
}
|
|
@@ -7442,8 +7575,8 @@ function addServiceNodes(graph, services) {
|
|
|
7442
7575
|
}
|
|
7443
7576
|
|
|
7444
7577
|
// src/extract/aliases.ts
|
|
7445
|
-
import
|
|
7446
|
-
import { promises as
|
|
7578
|
+
import path22 from "path";
|
|
7579
|
+
import { promises as fs20 } from "fs";
|
|
7447
7580
|
import { parseAllDocuments } from "yaml";
|
|
7448
7581
|
import { NodeType as NodeType16 } from "@neat.is/types";
|
|
7449
7582
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
@@ -7470,14 +7603,14 @@ function indexServicesByName(services) {
|
|
|
7470
7603
|
const map = /* @__PURE__ */ new Map();
|
|
7471
7604
|
for (const s of services) {
|
|
7472
7605
|
map.set(s.node.name, s.node.id);
|
|
7473
|
-
map.set(
|
|
7606
|
+
map.set(path22.basename(s.dir), s.node.id);
|
|
7474
7607
|
}
|
|
7475
7608
|
return map;
|
|
7476
7609
|
}
|
|
7477
7610
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
7478
7611
|
let composePath = null;
|
|
7479
7612
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
7480
|
-
const abs =
|
|
7613
|
+
const abs = path22.join(scanPath, name);
|
|
7481
7614
|
if (await exists(abs)) {
|
|
7482
7615
|
composePath = abs;
|
|
7483
7616
|
break;
|
|
@@ -7490,7 +7623,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
7490
7623
|
} catch (err) {
|
|
7491
7624
|
recordExtractionError(
|
|
7492
7625
|
"aliases compose",
|
|
7493
|
-
|
|
7626
|
+
path22.relative(scanPath, composePath),
|
|
7494
7627
|
err
|
|
7495
7628
|
);
|
|
7496
7629
|
return;
|
|
@@ -7533,11 +7666,11 @@ function parseDockerfileLabels(content) {
|
|
|
7533
7666
|
}
|
|
7534
7667
|
async function collectDockerfileAliases(graph, services) {
|
|
7535
7668
|
for (const service of services) {
|
|
7536
|
-
const dockerfilePath =
|
|
7669
|
+
const dockerfilePath = path22.join(service.dir, "Dockerfile");
|
|
7537
7670
|
if (!await exists(dockerfilePath)) continue;
|
|
7538
7671
|
let content;
|
|
7539
7672
|
try {
|
|
7540
|
-
content = await
|
|
7673
|
+
content = await fs20.readFile(dockerfilePath, "utf8");
|
|
7541
7674
|
} catch (err) {
|
|
7542
7675
|
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
7543
7676
|
continue;
|
|
@@ -7549,15 +7682,15 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
7549
7682
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
7550
7683
|
if (depth > max) return [];
|
|
7551
7684
|
const out = [];
|
|
7552
|
-
const entries = await
|
|
7685
|
+
const entries = await fs20.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
7553
7686
|
for (const entry of entries) {
|
|
7554
7687
|
if (entry.isDirectory()) {
|
|
7555
7688
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
7556
|
-
const child =
|
|
7689
|
+
const child = path22.join(start, entry.name);
|
|
7557
7690
|
if (await isPythonVenvDir(child)) continue;
|
|
7558
7691
|
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
7559
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
7560
|
-
out.push(
|
|
7692
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path22.extname(entry.name))) {
|
|
7693
|
+
out.push(path22.join(start, entry.name));
|
|
7561
7694
|
}
|
|
7562
7695
|
}
|
|
7563
7696
|
return out;
|
|
@@ -7584,7 +7717,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
7584
7717
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
7585
7718
|
const files = await walkYamlFiles(scanPath);
|
|
7586
7719
|
for (const file of files) {
|
|
7587
|
-
const content = await
|
|
7720
|
+
const content = await fs20.readFile(file, "utf8");
|
|
7588
7721
|
let docs;
|
|
7589
7722
|
try {
|
|
7590
7723
|
docs = parseAllDocuments(content).map((d) => d.toJSON());
|
|
@@ -7608,14 +7741,14 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
7608
7741
|
}
|
|
7609
7742
|
|
|
7610
7743
|
// src/extract/files.ts
|
|
7611
|
-
import
|
|
7744
|
+
import path23 from "path";
|
|
7612
7745
|
async function addFiles(graph, services) {
|
|
7613
7746
|
let nodesAdded = 0;
|
|
7614
7747
|
let edgesAdded = 0;
|
|
7615
7748
|
for (const service of services) {
|
|
7616
7749
|
const filePaths = await walkSourceFiles(service.dir, service.excludeDirs);
|
|
7617
7750
|
for (const filePath of filePaths) {
|
|
7618
|
-
const relPath = toPosix(
|
|
7751
|
+
const relPath = toPosix(path23.relative(service.dir, filePath));
|
|
7619
7752
|
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
7620
7753
|
graph,
|
|
7621
7754
|
service.pkg.name,
|
|
@@ -7630,7 +7763,7 @@ async function addFiles(graph, services) {
|
|
|
7630
7763
|
}
|
|
7631
7764
|
|
|
7632
7765
|
// src/extract/symbols.ts
|
|
7633
|
-
import
|
|
7766
|
+
import path24 from "path";
|
|
7634
7767
|
import Parser3 from "tree-sitter";
|
|
7635
7768
|
import JavaScript3 from "tree-sitter-javascript";
|
|
7636
7769
|
import TypeScript from "tree-sitter-typescript";
|
|
@@ -8389,10 +8522,10 @@ async function addSymbols(graph, services) {
|
|
|
8389
8522
|
for (const service of services) {
|
|
8390
8523
|
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
8391
8524
|
for (const file of files) {
|
|
8392
|
-
const ext =
|
|
8525
|
+
const ext = path24.extname(file.path);
|
|
8393
8526
|
const parser = parserForExt5(ext);
|
|
8394
8527
|
if (!parser) continue;
|
|
8395
|
-
const relPath = toPosix(
|
|
8528
|
+
const relPath = toPosix(path24.relative(service.dir, file.path));
|
|
8396
8529
|
let defs;
|
|
8397
8530
|
try {
|
|
8398
8531
|
const tree = parseSource3(parser, file.content);
|
|
@@ -8451,7 +8584,7 @@ async function addSymbols(graph, services) {
|
|
|
8451
8584
|
}
|
|
8452
8585
|
|
|
8453
8586
|
// src/extract/symbol-edges.ts
|
|
8454
|
-
import
|
|
8587
|
+
import path25 from "path";
|
|
8455
8588
|
import Parser4 from "tree-sitter";
|
|
8456
8589
|
import {
|
|
8457
8590
|
EdgeType as EdgeType8,
|
|
@@ -8565,10 +8698,10 @@ async function addSymbolEdges(graph, services) {
|
|
|
8565
8698
|
const tsPaths = await loadTsPathConfig(service.dir);
|
|
8566
8699
|
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
8567
8700
|
for (const file of files) {
|
|
8568
|
-
const parser = parserForExt5(
|
|
8701
|
+
const parser = parserForExt5(path25.extname(file.path));
|
|
8569
8702
|
if (!parser) continue;
|
|
8570
|
-
const relPath = toPosix(
|
|
8571
|
-
const fileDir =
|
|
8703
|
+
const relPath = toPosix(path25.relative(service.dir, file.path));
|
|
8704
|
+
const fileDir = path25.dirname(file.path);
|
|
8572
8705
|
let root;
|
|
8573
8706
|
try {
|
|
8574
8707
|
root = parseSource3(parser, file.content).rootNode;
|
|
@@ -8702,7 +8835,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
8702
8835
|
}
|
|
8703
8836
|
|
|
8704
8837
|
// src/extract/actions.ts
|
|
8705
|
-
import
|
|
8838
|
+
import path26 from "path";
|
|
8706
8839
|
import Parser5 from "tree-sitter";
|
|
8707
8840
|
import {
|
|
8708
8841
|
EdgeType as EdgeType9,
|
|
@@ -8856,9 +8989,9 @@ async function addServerActions(graph, services) {
|
|
|
8856
8989
|
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
8857
8990
|
for (const file of files) {
|
|
8858
8991
|
if (isTestPath(file.path)) continue;
|
|
8859
|
-
const parser = parserForExt5(
|
|
8992
|
+
const parser = parserForExt5(path26.extname(file.path));
|
|
8860
8993
|
if (!parser) continue;
|
|
8861
|
-
const relPath = toPosix(
|
|
8994
|
+
const relPath = toPosix(path26.relative(service.dir, file.path));
|
|
8862
8995
|
let root;
|
|
8863
8996
|
try {
|
|
8864
8997
|
root = parseSource3(parser, file.content).rootNode;
|
|
@@ -8919,10 +9052,10 @@ async function addServerActions(graph, services) {
|
|
|
8919
9052
|
}
|
|
8920
9053
|
for (const file of files) {
|
|
8921
9054
|
if (isTestPath(file.path)) continue;
|
|
8922
|
-
const parser = parserForExt5(
|
|
9055
|
+
const parser = parserForExt5(path26.extname(file.path));
|
|
8923
9056
|
if (!parser) continue;
|
|
8924
|
-
const relPath = toPosix(
|
|
8925
|
-
const fileDir =
|
|
9057
|
+
const relPath = toPosix(path26.relative(service.dir, file.path));
|
|
9058
|
+
const fileDir = path26.dirname(file.path);
|
|
8926
9059
|
let root;
|
|
8927
9060
|
try {
|
|
8928
9061
|
root = parseSource3(parser, file.content).rootNode;
|
|
@@ -8974,7 +9107,7 @@ async function addServerActions(graph, services) {
|
|
|
8974
9107
|
}
|
|
8975
9108
|
|
|
8976
9109
|
// src/extract/databases/index.ts
|
|
8977
|
-
import
|
|
9110
|
+
import path35 from "path";
|
|
8978
9111
|
import {
|
|
8979
9112
|
EdgeType as EdgeType10,
|
|
8980
9113
|
NodeType as NodeType20,
|
|
@@ -8985,9 +9118,9 @@ import {
|
|
|
8985
9118
|
} from "@neat.is/types";
|
|
8986
9119
|
|
|
8987
9120
|
// src/extract/databases/db-config-yaml.ts
|
|
8988
|
-
import
|
|
9121
|
+
import path27 from "path";
|
|
8989
9122
|
async function parse(serviceDir) {
|
|
8990
|
-
const yamlPath =
|
|
9123
|
+
const yamlPath = path27.join(serviceDir, "db-config.yaml");
|
|
8991
9124
|
if (!await exists(yamlPath)) return [];
|
|
8992
9125
|
const raw = await readYaml(yamlPath);
|
|
8993
9126
|
return [
|
|
@@ -9004,12 +9137,12 @@ async function parse(serviceDir) {
|
|
|
9004
9137
|
var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
9005
9138
|
|
|
9006
9139
|
// src/extract/databases/dotenv.ts
|
|
9007
|
-
import { promises as
|
|
9008
|
-
import
|
|
9140
|
+
import { promises as fs22 } from "fs";
|
|
9141
|
+
import path29 from "path";
|
|
9009
9142
|
|
|
9010
9143
|
// src/extract/databases/shared.ts
|
|
9011
|
-
import { promises as
|
|
9012
|
-
import
|
|
9144
|
+
import { promises as fs21 } from "fs";
|
|
9145
|
+
import path28 from "path";
|
|
9013
9146
|
function schemeToEngine(scheme) {
|
|
9014
9147
|
const s = scheme.toLowerCase().split("+")[0];
|
|
9015
9148
|
switch (s) {
|
|
@@ -9048,18 +9181,18 @@ function parseConnectionString(url) {
|
|
|
9048
9181
|
}
|
|
9049
9182
|
async function readIfExists(filePath) {
|
|
9050
9183
|
try {
|
|
9051
|
-
return await
|
|
9184
|
+
return await fs21.readFile(filePath, "utf8");
|
|
9052
9185
|
} catch {
|
|
9053
9186
|
return null;
|
|
9054
9187
|
}
|
|
9055
9188
|
}
|
|
9056
9189
|
async function resolveEnvVar(serviceDir, name) {
|
|
9057
|
-
const entries = await
|
|
9190
|
+
const entries = await fs21.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
9058
9191
|
const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
|
|
9059
9192
|
const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
|
|
9060
9193
|
envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
|
|
9061
9194
|
for (const fileName of envNames) {
|
|
9062
|
-
const content = await readIfExists(
|
|
9195
|
+
const content = await readIfExists(path28.join(serviceDir, fileName));
|
|
9063
9196
|
if (!content) continue;
|
|
9064
9197
|
for (const line of content.split("\n")) {
|
|
9065
9198
|
const trimmed = line.trim();
|
|
@@ -9078,7 +9211,7 @@ async function resolveEnvVar(serviceDir, name) {
|
|
|
9078
9211
|
}
|
|
9079
9212
|
async function findFirst(serviceDir, candidates) {
|
|
9080
9213
|
for (const rel of candidates) {
|
|
9081
|
-
const abs =
|
|
9214
|
+
const abs = path28.join(serviceDir, rel);
|
|
9082
9215
|
const content = await readIfExists(abs);
|
|
9083
9216
|
if (content !== null) return abs;
|
|
9084
9217
|
}
|
|
@@ -9136,15 +9269,15 @@ function parseDotenvLine(line) {
|
|
|
9136
9269
|
return { key, value };
|
|
9137
9270
|
}
|
|
9138
9271
|
async function parse2(serviceDir) {
|
|
9139
|
-
const entries = await
|
|
9272
|
+
const entries = await fs22.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
9140
9273
|
const configs = [];
|
|
9141
9274
|
const seen = /* @__PURE__ */ new Set();
|
|
9142
9275
|
for (const entry of entries) {
|
|
9143
9276
|
if (!entry.isFile()) continue;
|
|
9144
9277
|
const match = isConfigFile(entry.name);
|
|
9145
9278
|
if (!match.match || match.fileType !== "env") continue;
|
|
9146
|
-
const filePath =
|
|
9147
|
-
const content = await
|
|
9279
|
+
const filePath = path29.join(serviceDir, entry.name);
|
|
9280
|
+
const content = await fs22.readFile(filePath, "utf8");
|
|
9148
9281
|
for (const line of content.split("\n")) {
|
|
9149
9282
|
const parsed = parseDotenvLine(line);
|
|
9150
9283
|
if (!parsed) continue;
|
|
@@ -9162,9 +9295,9 @@ async function parse2(serviceDir) {
|
|
|
9162
9295
|
var dotenvParser = { name: ".env", parse: parse2 };
|
|
9163
9296
|
|
|
9164
9297
|
// src/extract/databases/prisma.ts
|
|
9165
|
-
import
|
|
9298
|
+
import path30 from "path";
|
|
9166
9299
|
async function parse3(serviceDir) {
|
|
9167
|
-
const schemaPath =
|
|
9300
|
+
const schemaPath = path30.join(serviceDir, "prisma", "schema.prisma");
|
|
9168
9301
|
const content = await readIfExists(schemaPath);
|
|
9169
9302
|
if (!content) return [];
|
|
9170
9303
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -9319,10 +9452,10 @@ async function parse5(serviceDir) {
|
|
|
9319
9452
|
var knexParser = { name: "knex", parse: parse5 };
|
|
9320
9453
|
|
|
9321
9454
|
// src/extract/databases/ormconfig.ts
|
|
9322
|
-
import
|
|
9455
|
+
import path31 from "path";
|
|
9323
9456
|
async function parse6(serviceDir) {
|
|
9324
9457
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
9325
|
-
const abs =
|
|
9458
|
+
const abs = path31.join(serviceDir, candidate);
|
|
9326
9459
|
if (!await exists(abs)) continue;
|
|
9327
9460
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
9328
9461
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -9380,9 +9513,9 @@ async function parse7(serviceDir) {
|
|
|
9380
9513
|
var typeormParser = { name: "typeorm", parse: parse7 };
|
|
9381
9514
|
|
|
9382
9515
|
// src/extract/databases/sequelize.ts
|
|
9383
|
-
import
|
|
9516
|
+
import path32 from "path";
|
|
9384
9517
|
async function parse8(serviceDir) {
|
|
9385
|
-
const configPath =
|
|
9518
|
+
const configPath = path32.join(serviceDir, "config", "config.json");
|
|
9386
9519
|
if (!await exists(configPath)) return [];
|
|
9387
9520
|
const raw = await readJson(configPath);
|
|
9388
9521
|
const out = [];
|
|
@@ -9408,8 +9541,8 @@ async function parse8(serviceDir) {
|
|
|
9408
9541
|
var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
9409
9542
|
|
|
9410
9543
|
// src/extract/databases/csharp.ts
|
|
9411
|
-
import { promises as
|
|
9412
|
-
import
|
|
9544
|
+
import { promises as fs23 } from "fs";
|
|
9545
|
+
import path33 from "path";
|
|
9413
9546
|
var CS_EXT = ".cs";
|
|
9414
9547
|
var NPGSQL_GATE = /\bUseNpgsql\b|\bNpgsql\b/;
|
|
9415
9548
|
var REDIS_GATE = /\bConnectionMultiplexer\b|\bStackExchange\.Redis\b|\bConfigurationOptions\.Parse\b|\bAddStackExchangeRedisCache\b/;
|
|
@@ -9456,12 +9589,12 @@ function parseRedisEndpoint(raw) {
|
|
|
9456
9589
|
return { host, port, database: "", engine: "redis", engineVersion: "unknown" };
|
|
9457
9590
|
}
|
|
9458
9591
|
async function resolveEnvUpTree(startDir, name) {
|
|
9459
|
-
let dir =
|
|
9592
|
+
let dir = path33.resolve(startDir);
|
|
9460
9593
|
for (let depth = 0; depth < 12; depth++) {
|
|
9461
9594
|
const value = await resolveEnvVar(dir, name);
|
|
9462
9595
|
if (value !== null) return value;
|
|
9463
|
-
const atRepoRoot = await
|
|
9464
|
-
const parent =
|
|
9596
|
+
const atRepoRoot = await fs23.access(path33.join(dir, ".git")).then(() => true).catch(() => false);
|
|
9597
|
+
const parent = path33.dirname(dir);
|
|
9465
9598
|
if (atRepoRoot || parent === dir) break;
|
|
9466
9599
|
dir = parent;
|
|
9467
9600
|
}
|
|
@@ -9516,12 +9649,12 @@ async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
|
|
|
9516
9649
|
}
|
|
9517
9650
|
async function parse9(serviceDir) {
|
|
9518
9651
|
const files = (await walkSourceFiles(serviceDir).catch(() => [])).filter(
|
|
9519
|
-
(f) =>
|
|
9652
|
+
(f) => path33.extname(f) === CS_EXT
|
|
9520
9653
|
);
|
|
9521
9654
|
if (files.length === 0) return [];
|
|
9522
9655
|
const sources = [];
|
|
9523
9656
|
for (const file of files) {
|
|
9524
|
-
const content = await
|
|
9657
|
+
const content = await fs23.readFile(file, "utf8").catch(() => null);
|
|
9525
9658
|
if (content !== null) sources.push({ file, content });
|
|
9526
9659
|
}
|
|
9527
9660
|
let pgGateFile = null;
|
|
@@ -9561,7 +9694,7 @@ async function parse9(serviceDir) {
|
|
|
9561
9694
|
var csharpParser = { name: "csharp", parse: parse9 };
|
|
9562
9695
|
|
|
9563
9696
|
// src/extract/databases/docker-compose.ts
|
|
9564
|
-
import
|
|
9697
|
+
import path34 from "path";
|
|
9565
9698
|
function portFromService(svc) {
|
|
9566
9699
|
for (const raw of svc.ports ?? []) {
|
|
9567
9700
|
const str = String(raw);
|
|
@@ -9588,7 +9721,7 @@ function databaseFromEnv(svc) {
|
|
|
9588
9721
|
}
|
|
9589
9722
|
async function parse10(serviceDir) {
|
|
9590
9723
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
9591
|
-
const abs =
|
|
9724
|
+
const abs = path34.join(serviceDir, name);
|
|
9592
9725
|
if (!await exists(abs)) continue;
|
|
9593
9726
|
const raw = await readYaml(abs);
|
|
9594
9727
|
if (!raw?.services) return [];
|
|
@@ -9761,7 +9894,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
9761
9894
|
discoveredVia: mergedDiscoveredVia
|
|
9762
9895
|
});
|
|
9763
9896
|
}
|
|
9764
|
-
const relConfigFile = toPosix(
|
|
9897
|
+
const relConfigFile = toPosix(path35.relative(service.dir, config.sourceFile));
|
|
9765
9898
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
9766
9899
|
graph,
|
|
9767
9900
|
service.pkg.name,
|
|
@@ -9770,7 +9903,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
9770
9903
|
);
|
|
9771
9904
|
nodesAdded += fn;
|
|
9772
9905
|
edgesAdded += fe;
|
|
9773
|
-
const evidenceFile = toPosix(
|
|
9906
|
+
const evidenceFile = toPosix(path35.relative(scanPath, config.sourceFile));
|
|
9774
9907
|
const edge = {
|
|
9775
9908
|
id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
|
|
9776
9909
|
source: fileNodeId,
|
|
@@ -9791,15 +9924,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
9791
9924
|
if (allConfigs.length === 1) {
|
|
9792
9925
|
const primary = allConfigs[0];
|
|
9793
9926
|
service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
|
|
9794
|
-
const relPath =
|
|
9927
|
+
const relPath = path35.relative(scanPath, primary.sourceFile);
|
|
9795
9928
|
const cfgId = configId(relPath);
|
|
9796
9929
|
if (!graph.hasNode(cfgId)) {
|
|
9797
9930
|
const cfgNode = {
|
|
9798
9931
|
id: cfgId,
|
|
9799
9932
|
type: NodeType20.ConfigNode,
|
|
9800
|
-
name:
|
|
9933
|
+
name: path35.basename(primary.sourceFile),
|
|
9801
9934
|
path: relPath,
|
|
9802
|
-
fileType: isConfigFile(
|
|
9935
|
+
fileType: isConfigFile(path35.basename(primary.sourceFile)).fileType || "config"
|
|
9803
9936
|
};
|
|
9804
9937
|
graph.addNode(cfgId, cfgNode);
|
|
9805
9938
|
nodesAdded++;
|
|
@@ -9839,8 +9972,8 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
9839
9972
|
}
|
|
9840
9973
|
|
|
9841
9974
|
// src/extract/configs.ts
|
|
9842
|
-
import { promises as
|
|
9843
|
-
import
|
|
9975
|
+
import { promises as fs24 } from "fs";
|
|
9976
|
+
import path36 from "path";
|
|
9844
9977
|
import {
|
|
9845
9978
|
EdgeType as EdgeType11,
|
|
9846
9979
|
NodeType as NodeType21,
|
|
@@ -9849,15 +9982,15 @@ import {
|
|
|
9849
9982
|
confidenceForExtracted as confidenceForExtracted8
|
|
9850
9983
|
} from "@neat.is/types";
|
|
9851
9984
|
async function walkConfigFiles(dir, excludeDirs = []) {
|
|
9852
|
-
const excluded = new Set(excludeDirs.map((d) =>
|
|
9985
|
+
const excluded = new Set(excludeDirs.map((d) => path36.resolve(d)));
|
|
9853
9986
|
const out = [];
|
|
9854
9987
|
async function walk11(current) {
|
|
9855
|
-
const entries = await
|
|
9988
|
+
const entries = await fs24.readdir(current, { withFileTypes: true });
|
|
9856
9989
|
for (const entry of entries) {
|
|
9857
|
-
const full =
|
|
9990
|
+
const full = path36.join(current, entry.name);
|
|
9858
9991
|
if (entry.isDirectory()) {
|
|
9859
9992
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
9860
|
-
if (excluded.has(
|
|
9993
|
+
if (excluded.has(path36.resolve(full))) continue;
|
|
9861
9994
|
if (await isPythonVenvDir(full)) continue;
|
|
9862
9995
|
await walk11(full);
|
|
9863
9996
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
@@ -9874,19 +10007,19 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
9874
10007
|
for (const service of services) {
|
|
9875
10008
|
const configFiles = await walkConfigFiles(service.dir, service.excludeDirs);
|
|
9876
10009
|
for (const file of configFiles) {
|
|
9877
|
-
const relPath =
|
|
10010
|
+
const relPath = path36.relative(scanPath, file);
|
|
9878
10011
|
const node = {
|
|
9879
10012
|
id: configId2(relPath),
|
|
9880
10013
|
type: NodeType21.ConfigNode,
|
|
9881
|
-
name:
|
|
10014
|
+
name: path36.basename(file),
|
|
9882
10015
|
path: relPath,
|
|
9883
|
-
fileType: isConfigFile(
|
|
10016
|
+
fileType: isConfigFile(path36.basename(file)).fileType
|
|
9884
10017
|
};
|
|
9885
10018
|
if (!graph.hasNode(node.id)) {
|
|
9886
10019
|
graph.addNode(node.id, node);
|
|
9887
10020
|
nodesAdded++;
|
|
9888
10021
|
}
|
|
9889
|
-
const relToService = toPosix(
|
|
10022
|
+
const relToService = toPosix(path36.relative(service.dir, file));
|
|
9890
10023
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
9891
10024
|
graph,
|
|
9892
10025
|
service.pkg.name,
|
|
@@ -9902,7 +10035,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
9902
10035
|
type: EdgeType11.CONFIGURED_BY,
|
|
9903
10036
|
provenance: Provenance11.EXTRACTED,
|
|
9904
10037
|
confidence: confidenceForExtracted8("structural"),
|
|
9905
|
-
evidence: { file: relPath.split(
|
|
10038
|
+
evidence: { file: relPath.split(path36.sep).join("/") }
|
|
9906
10039
|
};
|
|
9907
10040
|
if (!graph.hasEdge(edge.id)) {
|
|
9908
10041
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -9914,8 +10047,8 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
9914
10047
|
}
|
|
9915
10048
|
|
|
9916
10049
|
// src/extract/proto.ts
|
|
9917
|
-
import { promises as
|
|
9918
|
-
import
|
|
10050
|
+
import { promises as fs25 } from "fs";
|
|
10051
|
+
import path37 from "path";
|
|
9919
10052
|
import {
|
|
9920
10053
|
EdgeType as EdgeType12,
|
|
9921
10054
|
NodeType as NodeType22,
|
|
@@ -9960,18 +10093,18 @@ function grpcMethodsFromProto(content, fqPackage) {
|
|
|
9960
10093
|
return out;
|
|
9961
10094
|
}
|
|
9962
10095
|
async function walkProtoFiles(dir, excludeDirs = []) {
|
|
9963
|
-
const excluded = new Set(excludeDirs.map((d) =>
|
|
10096
|
+
const excluded = new Set(excludeDirs.map((d) => path37.resolve(d)));
|
|
9964
10097
|
const out = [];
|
|
9965
10098
|
async function walk11(current) {
|
|
9966
|
-
const entries = await
|
|
10099
|
+
const entries = await fs25.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
9967
10100
|
for (const entry of entries) {
|
|
9968
|
-
const full =
|
|
10101
|
+
const full = path37.join(current, entry.name);
|
|
9969
10102
|
if (entry.isDirectory()) {
|
|
9970
10103
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
9971
|
-
if (excluded.has(
|
|
10104
|
+
if (excluded.has(path37.resolve(full))) continue;
|
|
9972
10105
|
if (await isPythonVenvDir(full)) continue;
|
|
9973
10106
|
await walk11(full);
|
|
9974
|
-
} else if (entry.isFile() &&
|
|
10107
|
+
} else if (entry.isFile() && path37.extname(entry.name) === PROTO_EXTENSION) {
|
|
9975
10108
|
out.push(full);
|
|
9976
10109
|
}
|
|
9977
10110
|
}
|
|
@@ -9986,10 +10119,10 @@ async function addGrpcMethods(graph, services) {
|
|
|
9986
10119
|
const protoPaths = await walkProtoFiles(service.dir, service.excludeDirs);
|
|
9987
10120
|
for (const protoPath of protoPaths) {
|
|
9988
10121
|
if (isTestPath(protoPath)) continue;
|
|
9989
|
-
const relFile = toPosix(
|
|
10122
|
+
const relFile = toPosix(path37.relative(service.dir, protoPath));
|
|
9990
10123
|
let content;
|
|
9991
10124
|
try {
|
|
9992
|
-
content = await
|
|
10125
|
+
content = await fs25.readFile(protoPath, "utf8");
|
|
9993
10126
|
} catch (err) {
|
|
9994
10127
|
recordExtractionError("proto extraction", protoPath, err);
|
|
9995
10128
|
continue;
|
|
@@ -10052,7 +10185,7 @@ import {
|
|
|
10052
10185
|
} from "@neat.is/types";
|
|
10053
10186
|
|
|
10054
10187
|
// src/extract/calls/http.ts
|
|
10055
|
-
import
|
|
10188
|
+
import path38 from "path";
|
|
10056
10189
|
import Parser6 from "tree-sitter";
|
|
10057
10190
|
import JavaScript4 from "tree-sitter-javascript";
|
|
10058
10191
|
import TypeScript2 from "tree-sitter-typescript";
|
|
@@ -10140,7 +10273,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
10140
10273
|
const seen = /* @__PURE__ */ new Set();
|
|
10141
10274
|
for (const file of files) {
|
|
10142
10275
|
if (isTestPath(file.path)) continue;
|
|
10143
|
-
const parser = parserForExt(
|
|
10276
|
+
const parser = parserForExt(path38.extname(file.path), parserCache);
|
|
10144
10277
|
let sites;
|
|
10145
10278
|
try {
|
|
10146
10279
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -10149,7 +10282,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
10149
10282
|
continue;
|
|
10150
10283
|
}
|
|
10151
10284
|
if (sites.length === 0) continue;
|
|
10152
|
-
const relFile = toPosix(
|
|
10285
|
+
const relFile = toPosix(path38.relative(service.dir, file.path));
|
|
10153
10286
|
for (const site of sites) {
|
|
10154
10287
|
const targetId = hostToNodeId.get(site.host);
|
|
10155
10288
|
if (!targetId || targetId === service.node.id) continue;
|
|
@@ -10202,7 +10335,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
10202
10335
|
}
|
|
10203
10336
|
|
|
10204
10337
|
// src/extract/calls/route-match.ts
|
|
10205
|
-
import
|
|
10338
|
+
import path39 from "path";
|
|
10206
10339
|
import Parser7 from "tree-sitter";
|
|
10207
10340
|
import JavaScript5 from "tree-sitter-javascript";
|
|
10208
10341
|
import {
|
|
@@ -10487,7 +10620,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
10487
10620
|
const seen = /* @__PURE__ */ new Set();
|
|
10488
10621
|
for (const file of files) {
|
|
10489
10622
|
if (isTestPath(file.path)) continue;
|
|
10490
|
-
if (!JS_CLIENT_EXTENSIONS.has(
|
|
10623
|
+
if (!JS_CLIENT_EXTENSIONS.has(path39.extname(file.path))) continue;
|
|
10491
10624
|
let sites;
|
|
10492
10625
|
try {
|
|
10493
10626
|
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
@@ -10496,7 +10629,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
10496
10629
|
continue;
|
|
10497
10630
|
}
|
|
10498
10631
|
if (sites.length === 0) continue;
|
|
10499
|
-
const relFile = toPosix(
|
|
10632
|
+
const relFile = toPosix(path39.relative(service.dir, file.path));
|
|
10500
10633
|
for (const site of sites) {
|
|
10501
10634
|
if (site.host === null) {
|
|
10502
10635
|
const dedupKey2 = `${relFile}|unresolved-host`;
|
|
@@ -10588,7 +10721,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
10588
10721
|
}
|
|
10589
10722
|
|
|
10590
10723
|
// src/extract/calls/kafka.ts
|
|
10591
|
-
import
|
|
10724
|
+
import path40 from "path";
|
|
10592
10725
|
import { infraId as infraId2 } from "@neat.is/types";
|
|
10593
10726
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
10594
10727
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -10676,13 +10809,13 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
10676
10809
|
// call sites — verified-call-site tier (ADR-066).
|
|
10677
10810
|
confidenceKind: "verified-call-site",
|
|
10678
10811
|
evidence: {
|
|
10679
|
-
file:
|
|
10812
|
+
file: path40.relative(serviceDir, file.path),
|
|
10680
10813
|
line,
|
|
10681
10814
|
snippet: snippet(file.content, line)
|
|
10682
10815
|
}
|
|
10683
10816
|
});
|
|
10684
10817
|
};
|
|
10685
|
-
if (
|
|
10818
|
+
if (path40.extname(file.path) === ".go") {
|
|
10686
10819
|
goSaramaEndpoints(file.content, make);
|
|
10687
10820
|
} else {
|
|
10688
10821
|
for (const { topic } of findAll(PRODUCER_TOPIC_RE, file.content)) make(topic, "PUBLISHES_TO");
|
|
@@ -10692,7 +10825,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
10692
10825
|
}
|
|
10693
10826
|
|
|
10694
10827
|
// src/extract/calls/redis.ts
|
|
10695
|
-
import
|
|
10828
|
+
import path41 from "path";
|
|
10696
10829
|
import { infraId as infraId3 } from "@neat.is/types";
|
|
10697
10830
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
10698
10831
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -10715,7 +10848,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
10715
10848
|
// support tier (ADR-066).
|
|
10716
10849
|
confidenceKind: "url-with-structural-support",
|
|
10717
10850
|
evidence: {
|
|
10718
|
-
file:
|
|
10851
|
+
file: path41.relative(serviceDir, file.path),
|
|
10719
10852
|
line,
|
|
10720
10853
|
snippet: snippet(file.content, line)
|
|
10721
10854
|
}
|
|
@@ -10725,7 +10858,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
10725
10858
|
}
|
|
10726
10859
|
|
|
10727
10860
|
// src/extract/calls/aws.ts
|
|
10728
|
-
import
|
|
10861
|
+
import path42 from "path";
|
|
10729
10862
|
import { infraId as infraId4 } from "@neat.is/types";
|
|
10730
10863
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
10731
10864
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -10759,7 +10892,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
10759
10892
|
// (ADR-066).
|
|
10760
10893
|
confidenceKind: "verified-call-site",
|
|
10761
10894
|
evidence: {
|
|
10762
|
-
file:
|
|
10895
|
+
file: path42.relative(serviceDir, file.path),
|
|
10763
10896
|
line,
|
|
10764
10897
|
snippet: snippet(file.content, line)
|
|
10765
10898
|
}
|
|
@@ -10783,7 +10916,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
10783
10916
|
}
|
|
10784
10917
|
|
|
10785
10918
|
// src/extract/calls/grpc.ts
|
|
10786
|
-
import
|
|
10919
|
+
import path43 from "path";
|
|
10787
10920
|
import { infraId as infraId5 } from "@neat.is/types";
|
|
10788
10921
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
10789
10922
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
@@ -10842,7 +10975,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
10842
10975
|
// tier (ADR-066).
|
|
10843
10976
|
confidenceKind: "verified-call-site",
|
|
10844
10977
|
evidence: {
|
|
10845
|
-
file:
|
|
10978
|
+
file: path43.relative(serviceDir, file.path),
|
|
10846
10979
|
line,
|
|
10847
10980
|
snippet: snippet(file.content, line)
|
|
10848
10981
|
}
|
|
@@ -10852,7 +10985,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
10852
10985
|
}
|
|
10853
10986
|
|
|
10854
10987
|
// src/extract/calls/supabase.ts
|
|
10855
|
-
import
|
|
10988
|
+
import path44 from "path";
|
|
10856
10989
|
import { infraId as infraId6 } from "@neat.is/types";
|
|
10857
10990
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
10858
10991
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
@@ -10911,7 +11044,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
10911
11044
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
10912
11045
|
confidenceKind: "verified-call-site",
|
|
10913
11046
|
evidence: {
|
|
10914
|
-
file:
|
|
11047
|
+
file: path44.relative(serviceDir, file.path),
|
|
10915
11048
|
line,
|
|
10916
11049
|
snippet: snippet(file.content, line)
|
|
10917
11050
|
}
|
|
@@ -10938,7 +11071,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
10938
11071
|
edgeType: "CALLS",
|
|
10939
11072
|
confidenceKind: "verified-call-site",
|
|
10940
11073
|
evidence: {
|
|
10941
|
-
file:
|
|
11074
|
+
file: path44.relative(serviceDir, file.path),
|
|
10942
11075
|
line,
|
|
10943
11076
|
snippet: snippet(file.content, line)
|
|
10944
11077
|
}
|
|
@@ -10949,7 +11082,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
10949
11082
|
}
|
|
10950
11083
|
|
|
10951
11084
|
// src/extract/calls/firestore.ts
|
|
10952
|
-
import
|
|
11085
|
+
import path45 from "path";
|
|
10953
11086
|
import Parser8 from "tree-sitter";
|
|
10954
11087
|
import JavaScript6 from "tree-sitter-javascript";
|
|
10955
11088
|
import { infraId as infraId7 } from "@neat.is/types";
|
|
@@ -11122,7 +11255,7 @@ function firestoreEndpointsFromFile(file, serviceDir, imported) {
|
|
|
11122
11255
|
if (!hasClient && !hasAdmin && importedNames.size === 0) return [];
|
|
11123
11256
|
const ownSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
11124
11257
|
const fileSdk = !hasClient && !hasAdmin ? imported?.sdk ?? null : ownSdk;
|
|
11125
|
-
const tree = parseSource3(parserForExt2(
|
|
11258
|
+
const tree = parseSource3(parserForExt2(path45.extname(file.path)), file.content);
|
|
11126
11259
|
const clientVars = firestoreClientVars(tree.rootNode);
|
|
11127
11260
|
for (const n of importedNames) clientVars.add(n);
|
|
11128
11261
|
const collLine = /* @__PURE__ */ new Map();
|
|
@@ -11227,7 +11360,7 @@ function firestoreEndpointsFromFile(file, serviceDir, imported) {
|
|
|
11227
11360
|
...columnSet.size > 0 ? { columns: [...columnSet] } : {},
|
|
11228
11361
|
...sdkWrites ? { sdkWrites } : {},
|
|
11229
11362
|
evidence: {
|
|
11230
|
-
file:
|
|
11363
|
+
file: path45.relative(serviceDir, file.path),
|
|
11231
11364
|
line,
|
|
11232
11365
|
snippet: snippet(file.content, line)
|
|
11233
11366
|
}
|
|
@@ -11287,7 +11420,7 @@ function sdkOf(content) {
|
|
|
11287
11420
|
return hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
11288
11421
|
}
|
|
11289
11422
|
function parseOrNull(file) {
|
|
11290
|
-
const ext =
|
|
11423
|
+
const ext = path45.extname(file.path);
|
|
11291
11424
|
if (!(ext in GRAMMAR_BY_EXT)) return null;
|
|
11292
11425
|
try {
|
|
11293
11426
|
return parseSource3(parserForExt2(ext), file.content).rootNode;
|
|
@@ -11303,7 +11436,7 @@ async function firestoreCrossFileEndpoints(files, serviceDir) {
|
|
|
11303
11436
|
if (!root) continue;
|
|
11304
11437
|
const names = exportedFirestoreClients(root);
|
|
11305
11438
|
if (names.size === 0) continue;
|
|
11306
|
-
registry.set(toPosix(
|
|
11439
|
+
registry.set(toPosix(path45.relative(serviceDir, f.path)), { names, sdk: sdkOf(f.content) });
|
|
11307
11440
|
}
|
|
11308
11441
|
if (registry.size === 0) return [];
|
|
11309
11442
|
const out = [];
|
|
@@ -11316,7 +11449,7 @@ async function firestoreCrossFileEndpoints(files, serviceDir) {
|
|
|
11316
11449
|
const names = /* @__PURE__ */ new Set();
|
|
11317
11450
|
let sdk = null;
|
|
11318
11451
|
for (const b of bindings) {
|
|
11319
|
-
const rel = await resolveJsImport(b.specifier,
|
|
11452
|
+
const rel = await resolveJsImport(b.specifier, path45.dirname(f.path), serviceDir, null);
|
|
11320
11453
|
if (!rel) continue;
|
|
11321
11454
|
const entry = registry.get(rel);
|
|
11322
11455
|
if (!entry || !entry.names.has(b.imported)) continue;
|
|
@@ -11330,7 +11463,7 @@ async function firestoreCrossFileEndpoints(files, serviceDir) {
|
|
|
11330
11463
|
}
|
|
11331
11464
|
|
|
11332
11465
|
// src/extract/calls/mongoose.ts
|
|
11333
|
-
import
|
|
11466
|
+
import path46 from "path";
|
|
11334
11467
|
import { infraId as infraId8 } from "@neat.is/types";
|
|
11335
11468
|
var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
|
|
11336
11469
|
var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
|
|
@@ -11481,7 +11614,7 @@ function endpoint(r, file, serviceDir, matchText) {
|
|
|
11481
11614
|
kind: r.kind,
|
|
11482
11615
|
edgeType: "CALLS",
|
|
11483
11616
|
confidenceKind: "verified-call-site",
|
|
11484
|
-
evidence: { file:
|
|
11617
|
+
evidence: { file: path46.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
11485
11618
|
};
|
|
11486
11619
|
}
|
|
11487
11620
|
function mongooseEndpointsFromFile(file, serviceDir) {
|
|
@@ -11571,7 +11704,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
11571
11704
|
const registry = /* @__PURE__ */ new Map();
|
|
11572
11705
|
for (const f of mongooseFiles) {
|
|
11573
11706
|
const fx = fileExportsOf(f.content, pluralizeOn);
|
|
11574
|
-
if (fx) registry.set(toPosix(
|
|
11707
|
+
if (fx) registry.set(toPosix(path46.relative(serviceDir, f.path)), fx);
|
|
11575
11708
|
}
|
|
11576
11709
|
if (registry.size === 0) return [];
|
|
11577
11710
|
const out = [];
|
|
@@ -11582,7 +11715,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
11582
11715
|
const directColl = /* @__PURE__ */ new Map();
|
|
11583
11716
|
const nsExports = /* @__PURE__ */ new Map();
|
|
11584
11717
|
for (const b of bindings) {
|
|
11585
|
-
const resolvedRel = await resolveJsImport(b.specifier,
|
|
11718
|
+
const resolvedRel = await resolveJsImport(b.specifier, path46.dirname(f.path), serviceDir, null);
|
|
11586
11719
|
if (!resolvedRel) continue;
|
|
11587
11720
|
const fx = registry.get(resolvedRel);
|
|
11588
11721
|
if (!fx) continue;
|
|
@@ -11625,7 +11758,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
11625
11758
|
}
|
|
11626
11759
|
|
|
11627
11760
|
// src/extract/calls/sqlalchemy.ts
|
|
11628
|
-
import
|
|
11761
|
+
import path47 from "path";
|
|
11629
11762
|
import Parser9 from "tree-sitter";
|
|
11630
11763
|
import Python5 from "tree-sitter-python";
|
|
11631
11764
|
import { infraId as infraId9 } from "@neat.is/types";
|
|
@@ -11764,7 +11897,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
|
|
|
11764
11897
|
childTable,
|
|
11765
11898
|
parentTable,
|
|
11766
11899
|
evidence: {
|
|
11767
|
-
file:
|
|
11900
|
+
file: path47.relative(serviceDir, file.path),
|
|
11768
11901
|
line,
|
|
11769
11902
|
snippet: snippet(file.content, line)
|
|
11770
11903
|
}
|
|
@@ -11789,7 +11922,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
|
11789
11922
|
confidenceKind: "verified-call-site",
|
|
11790
11923
|
...columns && columns.length > 0 ? { columns } : {},
|
|
11791
11924
|
evidence: {
|
|
11792
|
-
file:
|
|
11925
|
+
file: path47.relative(serviceDir, file.path),
|
|
11793
11926
|
line,
|
|
11794
11927
|
snippet: snippet(file.content, line)
|
|
11795
11928
|
}
|
|
@@ -11896,7 +12029,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
11896
12029
|
edgeType: "CALLS",
|
|
11897
12030
|
confidenceKind: "verified-call-site",
|
|
11898
12031
|
evidence: {
|
|
11899
|
-
file:
|
|
12032
|
+
file: path47.relative(serviceDir, file.path),
|
|
11900
12033
|
line,
|
|
11901
12034
|
snippet: snippet(file.content, line)
|
|
11902
12035
|
}
|
|
@@ -11907,7 +12040,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
11907
12040
|
}
|
|
11908
12041
|
|
|
11909
12042
|
// src/extract/calls/socket.ts
|
|
11910
|
-
import
|
|
12043
|
+
import path48 from "path";
|
|
11911
12044
|
import Parser10 from "tree-sitter";
|
|
11912
12045
|
import Python6 from "tree-sitter-python";
|
|
11913
12046
|
import { infraId as infraId10 } from "@neat.is/types";
|
|
@@ -11983,7 +12116,7 @@ function socketEndpointsFromFile(file, serviceDir) {
|
|
|
11983
12116
|
// A verified `socket.socket(...)` + `.connect((host, port))` call site.
|
|
11984
12117
|
confidenceKind: "verified-call-site",
|
|
11985
12118
|
evidence: {
|
|
11986
|
-
file:
|
|
12119
|
+
file: path48.relative(serviceDir, file.path),
|
|
11987
12120
|
line,
|
|
11988
12121
|
snippet: snippet(file.content, line)
|
|
11989
12122
|
}
|
|
@@ -11993,7 +12126,7 @@ function socketEndpointsFromFile(file, serviceDir) {
|
|
|
11993
12126
|
}
|
|
11994
12127
|
|
|
11995
12128
|
// src/extract/calls/django-orm.ts
|
|
11996
|
-
import
|
|
12129
|
+
import path49 from "path";
|
|
11997
12130
|
import Parser11 from "tree-sitter";
|
|
11998
12131
|
import Python7 from "tree-sitter-python";
|
|
11999
12132
|
import { infraId as infraId11 } from "@neat.is/types";
|
|
@@ -12063,7 +12196,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
12063
12196
|
const tree = parseSource8(makePyParser5(), file.content);
|
|
12064
12197
|
const out = [];
|
|
12065
12198
|
const seen = /* @__PURE__ */ new Set();
|
|
12066
|
-
const defaultAppLabel =
|
|
12199
|
+
const defaultAppLabel = path49.basename(path49.dirname(file.path));
|
|
12067
12200
|
walk5(tree.rootNode, (node) => {
|
|
12068
12201
|
if (node.type !== "class_definition") return;
|
|
12069
12202
|
if (!extendsDjangoModel(node)) return;
|
|
@@ -12081,14 +12214,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
12081
12214
|
kind: "sql-table",
|
|
12082
12215
|
edgeType: "CALLS",
|
|
12083
12216
|
confidenceKind: "verified-call-site",
|
|
12084
|
-
evidence: { file:
|
|
12217
|
+
evidence: { file: path49.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
12085
12218
|
});
|
|
12086
12219
|
});
|
|
12087
12220
|
return out;
|
|
12088
12221
|
}
|
|
12089
12222
|
|
|
12090
12223
|
// src/extract/calls/drizzle.ts
|
|
12091
|
-
import
|
|
12224
|
+
import path50 from "path";
|
|
12092
12225
|
import Parser12 from "tree-sitter";
|
|
12093
12226
|
import JavaScript7 from "tree-sitter-javascript";
|
|
12094
12227
|
import { infraId as infraId12 } from "@neat.is/types";
|
|
@@ -12166,7 +12299,7 @@ function columnsFromObject(obj) {
|
|
|
12166
12299
|
}
|
|
12167
12300
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
12168
12301
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
12169
|
-
const tree = parseSource3(parserForExt3(
|
|
12302
|
+
const tree = parseSource3(parserForExt3(path50.extname(file.path)), file.content);
|
|
12170
12303
|
const out = [];
|
|
12171
12304
|
const seen = /* @__PURE__ */ new Set();
|
|
12172
12305
|
const walk11 = (node) => {
|
|
@@ -12189,7 +12322,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
12189
12322
|
confidenceKind: "structural",
|
|
12190
12323
|
columns,
|
|
12191
12324
|
evidence: {
|
|
12192
|
-
file:
|
|
12325
|
+
file: path50.relative(serviceDir, file.path),
|
|
12193
12326
|
line,
|
|
12194
12327
|
snippet: snippet(file.content, line)
|
|
12195
12328
|
}
|
|
@@ -12255,7 +12388,7 @@ function referencesTargetVar(call) {
|
|
|
12255
12388
|
}
|
|
12256
12389
|
function drizzleForeignKeys(file, serviceDir) {
|
|
12257
12390
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
12258
|
-
const tree = parseSource3(parserForExt3(
|
|
12391
|
+
const tree = parseSource3(parserForExt3(path50.extname(file.path)), file.content);
|
|
12259
12392
|
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
12260
12393
|
const out = [];
|
|
12261
12394
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -12274,7 +12407,7 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
12274
12407
|
childTable: table.tableName,
|
|
12275
12408
|
parentTable,
|
|
12276
12409
|
evidence: {
|
|
12277
|
-
file:
|
|
12410
|
+
file: path50.relative(serviceDir, file.path),
|
|
12278
12411
|
line,
|
|
12279
12412
|
snippet: snippet(file.content, line)
|
|
12280
12413
|
}
|
|
@@ -12290,7 +12423,7 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
12290
12423
|
}
|
|
12291
12424
|
|
|
12292
12425
|
// src/extract/calls/prisma.ts
|
|
12293
|
-
import
|
|
12426
|
+
import path51 from "path";
|
|
12294
12427
|
import { infraId as infraId13 } from "@neat.is/types";
|
|
12295
12428
|
var SCALAR_TYPES = /* @__PURE__ */ new Set([
|
|
12296
12429
|
"Int",
|
|
@@ -12346,7 +12479,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
12346
12479
|
confidenceKind: "structural",
|
|
12347
12480
|
columns: b.columns,
|
|
12348
12481
|
evidence: {
|
|
12349
|
-
file:
|
|
12482
|
+
file: path51.relative(serviceDir, file.path),
|
|
12350
12483
|
line: b.startLine,
|
|
12351
12484
|
snippet: snippet(content, b.startLine)
|
|
12352
12485
|
}
|
|
@@ -12407,7 +12540,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
12407
12540
|
}
|
|
12408
12541
|
async function prismaColumnEndpoints(serviceDir) {
|
|
12409
12542
|
const schemaPath = await findFirst(serviceDir, [
|
|
12410
|
-
|
|
12543
|
+
path51.join("prisma", "schema.prisma"),
|
|
12411
12544
|
"schema.prisma"
|
|
12412
12545
|
]);
|
|
12413
12546
|
if (!schemaPath) return [];
|
|
@@ -12482,7 +12615,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
12482
12615
|
childTable: current.table,
|
|
12483
12616
|
parentTable,
|
|
12484
12617
|
evidence: {
|
|
12485
|
-
file:
|
|
12618
|
+
file: path51.relative(serviceDir, file.path),
|
|
12486
12619
|
line: lineNo,
|
|
12487
12620
|
snippet: snippet(content, lineNo)
|
|
12488
12621
|
}
|
|
@@ -12497,7 +12630,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
12497
12630
|
}
|
|
12498
12631
|
async function prismaForeignKeys(serviceDir) {
|
|
12499
12632
|
const schemaPath = await findFirst(serviceDir, [
|
|
12500
|
-
|
|
12633
|
+
path51.join("prisma", "schema.prisma"),
|
|
12501
12634
|
"schema.prisma"
|
|
12502
12635
|
]);
|
|
12503
12636
|
if (!schemaPath) return [];
|
|
@@ -12507,7 +12640,7 @@ async function prismaForeignKeys(serviceDir) {
|
|
|
12507
12640
|
}
|
|
12508
12641
|
|
|
12509
12642
|
// src/extract/calls/activerecord.ts
|
|
12510
|
-
import
|
|
12643
|
+
import path52 from "path";
|
|
12511
12644
|
import Parser13 from "tree-sitter";
|
|
12512
12645
|
import Ruby3 from "tree-sitter-ruby";
|
|
12513
12646
|
import { infraId as infraId14 } from "@neat.is/types";
|
|
@@ -12715,7 +12848,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
|
|
|
12715
12848
|
confidenceKind: "structural",
|
|
12716
12849
|
...table.columns.length > 0 ? { columns: table.columns } : {},
|
|
12717
12850
|
evidence: {
|
|
12718
|
-
file:
|
|
12851
|
+
file: path52.relative(serviceDir, file.path),
|
|
12719
12852
|
line: table.line,
|
|
12720
12853
|
snippet: snippet(file.content, table.line)
|
|
12721
12854
|
}
|
|
@@ -12737,7 +12870,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
|
|
|
12737
12870
|
childTable,
|
|
12738
12871
|
parentTable,
|
|
12739
12872
|
evidence: {
|
|
12740
|
-
file:
|
|
12873
|
+
file: path52.relative(serviceDir, file.path),
|
|
12741
12874
|
line,
|
|
12742
12875
|
snippet: snippet(file.content, line)
|
|
12743
12876
|
}
|
|
@@ -12820,7 +12953,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
|
|
|
12820
12953
|
edgeType: "CALLS",
|
|
12821
12954
|
confidenceKind: "verified-call-site",
|
|
12822
12955
|
evidence: {
|
|
12823
|
-
file:
|
|
12956
|
+
file: path52.relative(serviceDir, file.path),
|
|
12824
12957
|
line,
|
|
12825
12958
|
snippet: snippet(file.content, line)
|
|
12826
12959
|
}
|
|
@@ -12857,7 +12990,7 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
12857
12990
|
childTable,
|
|
12858
12991
|
parentTable,
|
|
12859
12992
|
evidence: {
|
|
12860
|
-
file:
|
|
12993
|
+
file: path52.relative(serviceDir, file.path),
|
|
12861
12994
|
line,
|
|
12862
12995
|
snippet: snippet(file.content, line)
|
|
12863
12996
|
}
|
|
@@ -12868,7 +13001,7 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
12868
13001
|
}
|
|
12869
13002
|
|
|
12870
13003
|
// src/extract/calls/eloquent.ts
|
|
12871
|
-
import
|
|
13004
|
+
import path53 from "path";
|
|
12872
13005
|
import Parser14 from "tree-sitter";
|
|
12873
13006
|
import Php3 from "tree-sitter-php";
|
|
12874
13007
|
import { infraId as infraId15 } from "@neat.is/types";
|
|
@@ -13125,7 +13258,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
|
|
|
13125
13258
|
confidenceKind: "structural",
|
|
13126
13259
|
...columns.length > 0 ? { columns } : {},
|
|
13127
13260
|
evidence: {
|
|
13128
|
-
file:
|
|
13261
|
+
file: path53.relative(serviceDir, file.path),
|
|
13129
13262
|
line: bp.line,
|
|
13130
13263
|
snippet: snippet(file.content, bp.line)
|
|
13131
13264
|
}
|
|
@@ -13147,7 +13280,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
|
|
|
13147
13280
|
childTable,
|
|
13148
13281
|
parentTable,
|
|
13149
13282
|
evidence: {
|
|
13150
|
-
file:
|
|
13283
|
+
file: path53.relative(serviceDir, file.path),
|
|
13151
13284
|
line,
|
|
13152
13285
|
snippet: snippet(file.content, line)
|
|
13153
13286
|
}
|
|
@@ -13267,7 +13400,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
|
|
|
13267
13400
|
edgeType: "CALLS",
|
|
13268
13401
|
confidenceKind: "verified-call-site",
|
|
13269
13402
|
evidence: {
|
|
13270
|
-
file:
|
|
13403
|
+
file: path53.relative(serviceDir, file.path),
|
|
13271
13404
|
line,
|
|
13272
13405
|
snippet: snippet(file.content, line)
|
|
13273
13406
|
}
|
|
@@ -13303,7 +13436,7 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
13303
13436
|
childTable,
|
|
13304
13437
|
parentTable,
|
|
13305
13438
|
evidence: {
|
|
13306
|
-
file:
|
|
13439
|
+
file: path53.relative(serviceDir, file.path),
|
|
13307
13440
|
line,
|
|
13308
13441
|
snippet: snippet(file.content, line)
|
|
13309
13442
|
}
|
|
@@ -13314,7 +13447,7 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
13314
13447
|
}
|
|
13315
13448
|
|
|
13316
13449
|
// src/extract/calls/go.ts
|
|
13317
|
-
import
|
|
13450
|
+
import path54 from "path";
|
|
13318
13451
|
import Parser15 from "tree-sitter";
|
|
13319
13452
|
import Go4 from "tree-sitter-go";
|
|
13320
13453
|
import { infraId as infraId16 } from "@neat.is/types";
|
|
@@ -13388,7 +13521,7 @@ function firstStringLiteralArg(argsNode) {
|
|
|
13388
13521
|
return null;
|
|
13389
13522
|
}
|
|
13390
13523
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
13391
|
-
if (
|
|
13524
|
+
if (path54.extname(file.path) !== ".go") return [];
|
|
13392
13525
|
if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
|
|
13393
13526
|
const tree = parseSource11(makeGoParser3(), file.content);
|
|
13394
13527
|
const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
|
|
@@ -13417,7 +13550,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
13417
13550
|
confidenceKind: "verified-call-site",
|
|
13418
13551
|
...columns.length > 0 ? { columns } : {},
|
|
13419
13552
|
evidence: {
|
|
13420
|
-
file: toPosix(
|
|
13553
|
+
file: toPosix(path54.relative(serviceDir, file.path)),
|
|
13421
13554
|
line,
|
|
13422
13555
|
snippet: snippet(file.content, line)
|
|
13423
13556
|
}
|
|
@@ -13427,7 +13560,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
13427
13560
|
}
|
|
13428
13561
|
|
|
13429
13562
|
// src/extract/calls/gorm.ts
|
|
13430
|
-
import
|
|
13563
|
+
import path55 from "path";
|
|
13431
13564
|
import Parser16 from "tree-sitter";
|
|
13432
13565
|
import Go5 from "tree-sitter-go";
|
|
13433
13566
|
import { infraId as infraId17 } from "@neat.is/types";
|
|
@@ -13860,7 +13993,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
13860
13993
|
seen.delete(struct.name);
|
|
13861
13994
|
}
|
|
13862
13995
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
13863
|
-
if (
|
|
13996
|
+
if (path55.extname(file.path) !== ".go") return [];
|
|
13864
13997
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
13865
13998
|
const tree = parseSource12(makeGoParser4(), file.content);
|
|
13866
13999
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -13882,7 +14015,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
13882
14015
|
confidenceKind: "structural",
|
|
13883
14016
|
...columns.length > 0 ? { columns } : {},
|
|
13884
14017
|
evidence: {
|
|
13885
|
-
file: toPosix(
|
|
14018
|
+
file: toPosix(path55.relative(serviceDir, file.path)),
|
|
13886
14019
|
line: struct.line,
|
|
13887
14020
|
snippet: snippet(file.content, struct.line)
|
|
13888
14021
|
}
|
|
@@ -13891,7 +14024,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
13891
14024
|
return out;
|
|
13892
14025
|
}
|
|
13893
14026
|
function gormForeignKeys(file, serviceDir) {
|
|
13894
|
-
if (
|
|
14027
|
+
if (path55.extname(file.path) !== ".go") return [];
|
|
13895
14028
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
13896
14029
|
const tree = parseSource12(makeGoParser4(), file.content);
|
|
13897
14030
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -13906,7 +14039,7 @@ function gormForeignKeys(file, serviceDir) {
|
|
|
13906
14039
|
childTable,
|
|
13907
14040
|
parentTable,
|
|
13908
14041
|
evidence: {
|
|
13909
|
-
file: toPosix(
|
|
14042
|
+
file: toPosix(path55.relative(serviceDir, file.path)),
|
|
13910
14043
|
line,
|
|
13911
14044
|
snippet: snippet(file.content, line)
|
|
13912
14045
|
}
|
|
@@ -13942,7 +14075,7 @@ function gormForeignKeys(file, serviceDir) {
|
|
|
13942
14075
|
}
|
|
13943
14076
|
|
|
13944
14077
|
// src/extract/calls/efcore.ts
|
|
13945
|
-
import
|
|
14078
|
+
import path56 from "path";
|
|
13946
14079
|
import Parser17 from "tree-sitter";
|
|
13947
14080
|
import CSharp2 from "tree-sitter-c-sharp";
|
|
13948
14081
|
import { infraId as infraId18 } from "@neat.is/types";
|
|
@@ -14021,7 +14154,7 @@ function tableFromToTable(call) {
|
|
|
14021
14154
|
return value ? csStringLiteral(value) : null;
|
|
14022
14155
|
}
|
|
14023
14156
|
function efcoreEndpointsFromFile(file, serviceDir) {
|
|
14024
|
-
if (
|
|
14157
|
+
if (path56.extname(file.path) !== ".cs") return [];
|
|
14025
14158
|
if (!EFCORE_GATE.test(file.content)) return [];
|
|
14026
14159
|
const tree = parseSource13(makeCsParser(), file.content);
|
|
14027
14160
|
const out = [];
|
|
@@ -14036,7 +14169,7 @@ function efcoreEndpointsFromFile(file, serviceDir) {
|
|
|
14036
14169
|
edgeType: "CALLS",
|
|
14037
14170
|
confidenceKind: "structural",
|
|
14038
14171
|
evidence: {
|
|
14039
|
-
file: toPosix(
|
|
14172
|
+
file: toPosix(path56.relative(serviceDir, file.path)),
|
|
14040
14173
|
line,
|
|
14041
14174
|
snippet: snippet(file.content, line)
|
|
14042
14175
|
}
|
|
@@ -14293,7 +14426,7 @@ function ensureTableNode(graph, id, name) {
|
|
|
14293
14426
|
}
|
|
14294
14427
|
|
|
14295
14428
|
// src/extract/infra/docker-compose.ts
|
|
14296
|
-
import
|
|
14429
|
+
import path57 from "path";
|
|
14297
14430
|
import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
|
|
14298
14431
|
|
|
14299
14432
|
// src/extract/infra/shared.ts
|
|
@@ -14363,7 +14496,7 @@ function dependsOnList(value) {
|
|
|
14363
14496
|
}
|
|
14364
14497
|
function serviceNameToServiceNode(name, services) {
|
|
14365
14498
|
for (const s of services) {
|
|
14366
|
-
if (s.node.name === name ||
|
|
14499
|
+
if (s.node.name === name || path57.basename(s.dir) === name) return s.node.id;
|
|
14367
14500
|
}
|
|
14368
14501
|
return null;
|
|
14369
14502
|
}
|
|
@@ -14372,7 +14505,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
14372
14505
|
let edgesAdded = 0;
|
|
14373
14506
|
let composePath = null;
|
|
14374
14507
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
14375
|
-
const abs =
|
|
14508
|
+
const abs = path57.join(scanPath, name);
|
|
14376
14509
|
if (await exists(abs)) {
|
|
14377
14510
|
composePath = abs;
|
|
14378
14511
|
break;
|
|
@@ -14385,13 +14518,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
14385
14518
|
} catch (err) {
|
|
14386
14519
|
recordExtractionError(
|
|
14387
14520
|
"infra docker-compose",
|
|
14388
|
-
|
|
14521
|
+
path57.relative(scanPath, composePath),
|
|
14389
14522
|
err
|
|
14390
14523
|
);
|
|
14391
14524
|
return { nodesAdded, edgesAdded };
|
|
14392
14525
|
}
|
|
14393
14526
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
14394
|
-
const evidenceFile =
|
|
14527
|
+
const evidenceFile = path57.relative(scanPath, composePath).split(path57.sep).join("/");
|
|
14395
14528
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
14396
14529
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
14397
14530
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -14432,8 +14565,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
14432
14565
|
}
|
|
14433
14566
|
|
|
14434
14567
|
// src/extract/infra/dockerfile.ts
|
|
14435
|
-
import
|
|
14436
|
-
import { promises as
|
|
14568
|
+
import path58 from "path";
|
|
14569
|
+
import { promises as fs26 } from "fs";
|
|
14437
14570
|
import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
|
|
14438
14571
|
function readDockerfile(content) {
|
|
14439
14572
|
let image = null;
|
|
@@ -14463,15 +14596,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
14463
14596
|
let nodesAdded = 0;
|
|
14464
14597
|
let edgesAdded = 0;
|
|
14465
14598
|
for (const service of services) {
|
|
14466
|
-
const dockerfilePath =
|
|
14599
|
+
const dockerfilePath = path58.join(service.dir, "Dockerfile");
|
|
14467
14600
|
if (!await exists(dockerfilePath)) continue;
|
|
14468
14601
|
let content;
|
|
14469
14602
|
try {
|
|
14470
|
-
content = await
|
|
14603
|
+
content = await fs26.readFile(dockerfilePath, "utf8");
|
|
14471
14604
|
} catch (err) {
|
|
14472
14605
|
recordExtractionError(
|
|
14473
14606
|
"infra dockerfile",
|
|
14474
|
-
|
|
14607
|
+
path58.relative(scanPath, dockerfilePath),
|
|
14475
14608
|
err
|
|
14476
14609
|
);
|
|
14477
14610
|
continue;
|
|
@@ -14483,8 +14616,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
14483
14616
|
graph.addNode(node.id, node);
|
|
14484
14617
|
nodesAdded++;
|
|
14485
14618
|
}
|
|
14486
|
-
const relDockerfile = toPosix(
|
|
14487
|
-
const evidenceFile = toPosix(
|
|
14619
|
+
const relDockerfile = toPosix(path58.relative(service.dir, dockerfilePath));
|
|
14620
|
+
const evidenceFile = toPosix(path58.relative(scanPath, dockerfilePath));
|
|
14488
14621
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
14489
14622
|
graph,
|
|
14490
14623
|
service.pkg.name,
|
|
@@ -14535,23 +14668,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
14535
14668
|
}
|
|
14536
14669
|
|
|
14537
14670
|
// src/extract/infra/terraform.ts
|
|
14538
|
-
import { promises as
|
|
14539
|
-
import
|
|
14671
|
+
import { promises as fs27 } from "fs";
|
|
14672
|
+
import path59 from "path";
|
|
14540
14673
|
import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
|
|
14541
14674
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
14542
14675
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
14543
14676
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
14544
14677
|
if (depth > max) return [];
|
|
14545
14678
|
const out = [];
|
|
14546
|
-
const entries = await
|
|
14679
|
+
const entries = await fs27.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
14547
14680
|
for (const entry of entries) {
|
|
14548
14681
|
if (entry.isDirectory()) {
|
|
14549
14682
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
14550
|
-
const child =
|
|
14683
|
+
const child = path59.join(start, entry.name);
|
|
14551
14684
|
if (await isPythonVenvDir(child)) continue;
|
|
14552
14685
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
14553
14686
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
14554
|
-
out.push(
|
|
14687
|
+
out.push(path59.join(start, entry.name));
|
|
14555
14688
|
}
|
|
14556
14689
|
}
|
|
14557
14690
|
return out;
|
|
@@ -14582,8 +14715,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
14582
14715
|
let edgesAdded = 0;
|
|
14583
14716
|
const files = await walkTfFiles(scanPath);
|
|
14584
14717
|
for (const file of files) {
|
|
14585
|
-
const content = await
|
|
14586
|
-
const evidenceFile = toPosix(
|
|
14718
|
+
const content = await fs27.readFile(file, "utf8");
|
|
14719
|
+
const evidenceFile = toPosix(path59.relative(scanPath, file));
|
|
14587
14720
|
const resources = [];
|
|
14588
14721
|
const byKey = /* @__PURE__ */ new Map();
|
|
14589
14722
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -14639,8 +14772,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
14639
14772
|
}
|
|
14640
14773
|
|
|
14641
14774
|
// src/extract/infra/k8s.ts
|
|
14642
|
-
import { promises as
|
|
14643
|
-
import
|
|
14775
|
+
import { promises as fs28 } from "fs";
|
|
14776
|
+
import path60 from "path";
|
|
14644
14777
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
14645
14778
|
import { EdgeType as EdgeType20, serviceId as serviceId13 } from "@neat.is/types";
|
|
14646
14779
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
@@ -14656,15 +14789,15 @@ var WORKLOAD_KINDS = /* @__PURE__ */ new Set(["Deployment", "StatefulSet", "Daem
|
|
|
14656
14789
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
14657
14790
|
if (depth > max) return [];
|
|
14658
14791
|
const out = [];
|
|
14659
|
-
const entries = await
|
|
14792
|
+
const entries = await fs28.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
14660
14793
|
for (const entry of entries) {
|
|
14661
14794
|
if (entry.isDirectory()) {
|
|
14662
14795
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
14663
|
-
const child =
|
|
14796
|
+
const child = path60.join(start, entry.name);
|
|
14664
14797
|
if (await isPythonVenvDir(child)) continue;
|
|
14665
14798
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
14666
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
14667
|
-
out.push(
|
|
14799
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path60.extname(entry.name))) {
|
|
14800
|
+
out.push(path60.join(start, entry.name));
|
|
14668
14801
|
}
|
|
14669
14802
|
}
|
|
14670
14803
|
return out;
|
|
@@ -14674,14 +14807,14 @@ async function addK8sResources(graph, scanPath) {
|
|
|
14674
14807
|
let edgesAdded = 0;
|
|
14675
14808
|
const files = await walkYamlFiles2(scanPath);
|
|
14676
14809
|
for (const file of files) {
|
|
14677
|
-
const content = await
|
|
14810
|
+
const content = await fs28.readFile(file, "utf8");
|
|
14678
14811
|
let docs;
|
|
14679
14812
|
try {
|
|
14680
14813
|
docs = parseAllDocuments2(content).map((d) => d.toJSON());
|
|
14681
14814
|
} catch {
|
|
14682
14815
|
continue;
|
|
14683
14816
|
}
|
|
14684
|
-
const relFile = toPosix(
|
|
14817
|
+
const relFile = toPosix(path60.relative(scanPath, file));
|
|
14685
14818
|
for (const doc of docs) {
|
|
14686
14819
|
if (!doc?.kind || !doc.metadata?.name) continue;
|
|
14687
14820
|
const infraKind = K8S_KIND_TO_INFRA_KIND[doc.kind];
|
|
@@ -14729,16 +14862,16 @@ async function addK8sResources(graph, scanPath) {
|
|
|
14729
14862
|
}
|
|
14730
14863
|
|
|
14731
14864
|
// src/extract/infra/cloudflare.ts
|
|
14732
|
-
import { promises as
|
|
14733
|
-
import
|
|
14865
|
+
import { promises as fs29 } from "fs";
|
|
14866
|
+
import path61 from "path";
|
|
14734
14867
|
import { parse as parseToml3 } from "smol-toml";
|
|
14735
14868
|
import { EdgeType as EdgeType21, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
|
|
14736
14869
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
14737
14870
|
async function readWranglerConfig(dir) {
|
|
14738
14871
|
for (const filename of WRANGLER_FILENAMES) {
|
|
14739
|
-
const abs =
|
|
14872
|
+
const abs = path61.join(dir, filename);
|
|
14740
14873
|
if (!await exists(abs)) continue;
|
|
14741
|
-
const raw = await
|
|
14874
|
+
const raw = await fs29.readFile(abs, "utf8");
|
|
14742
14875
|
const config = filename === "wrangler.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
14743
14876
|
return { config, relFile: filename, raw };
|
|
14744
14877
|
}
|
|
@@ -14799,11 +14932,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
14799
14932
|
try {
|
|
14800
14933
|
read = await readWranglerConfig(service.dir);
|
|
14801
14934
|
} catch (err) {
|
|
14802
|
-
recordExtractionError("infra cloudflare",
|
|
14935
|
+
recordExtractionError("infra cloudflare", path61.relative(scanPath, service.dir), err);
|
|
14803
14936
|
continue;
|
|
14804
14937
|
}
|
|
14805
14938
|
if (!read || !read.config.name) continue;
|
|
14806
|
-
const evidenceFile = toPosix(
|
|
14939
|
+
const evidenceFile = toPosix(path61.relative(scanPath, path61.join(service.dir, read.relFile)));
|
|
14807
14940
|
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
14808
14941
|
}
|
|
14809
14942
|
for (const worker of discovered) {
|
|
@@ -14815,7 +14948,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
14815
14948
|
}
|
|
14816
14949
|
let anchorId = service.node.id;
|
|
14817
14950
|
if (config.main) {
|
|
14818
|
-
const entryRelPath = toPosix(
|
|
14951
|
+
const entryRelPath = toPosix(path61.normalize(config.main));
|
|
14819
14952
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
14820
14953
|
graph,
|
|
14821
14954
|
service.pkg.name,
|
|
@@ -14961,24 +15094,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
14961
15094
|
}
|
|
14962
15095
|
|
|
14963
15096
|
// src/extract/infra/vercel.ts
|
|
14964
|
-
import { promises as
|
|
14965
|
-
import
|
|
15097
|
+
import { promises as fs30 } from "fs";
|
|
15098
|
+
import path62 from "path";
|
|
14966
15099
|
import { EdgeType as EdgeType22 } from "@neat.is/types";
|
|
14967
15100
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
14968
15101
|
async function readVercelConfig(dir) {
|
|
14969
15102
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
14970
|
-
const abs =
|
|
15103
|
+
const abs = path62.join(dir, filename);
|
|
14971
15104
|
if (!await exists(abs)) continue;
|
|
14972
|
-
const raw = await
|
|
15105
|
+
const raw = await fs30.readFile(abs, "utf8");
|
|
14973
15106
|
const config = JSON.parse(maskCommentsInSource(raw));
|
|
14974
15107
|
return { config, relFile: filename, raw };
|
|
14975
15108
|
}
|
|
14976
15109
|
return null;
|
|
14977
15110
|
}
|
|
14978
15111
|
async function readLinkedProjectName(dir) {
|
|
14979
|
-
const abs =
|
|
15112
|
+
const abs = path62.join(dir, ".vercel", "project.json");
|
|
14980
15113
|
if (!await exists(abs)) return void 0;
|
|
14981
|
-
const parsed = JSON.parse(await
|
|
15114
|
+
const parsed = JSON.parse(await fs30.readFile(abs, "utf8"));
|
|
14982
15115
|
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
14983
15116
|
}
|
|
14984
15117
|
function routeSource(route) {
|
|
@@ -14994,7 +15127,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
14994
15127
|
read = await readVercelConfig(service.dir);
|
|
14995
15128
|
projectName = await readLinkedProjectName(service.dir);
|
|
14996
15129
|
} catch (err) {
|
|
14997
|
-
recordExtractionError("infra vercel",
|
|
15130
|
+
recordExtractionError("infra vercel", path62.relative(scanPath, service.dir), err);
|
|
14998
15131
|
continue;
|
|
14999
15132
|
}
|
|
15000
15133
|
if (!read && !projectName) continue;
|
|
@@ -15010,7 +15143,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
15010
15143
|
const anchorId = service.node.id;
|
|
15011
15144
|
if (!read) continue;
|
|
15012
15145
|
const { config, relFile, raw } = read;
|
|
15013
|
-
const evidenceFile = toPosix(
|
|
15146
|
+
const evidenceFile = toPosix(path62.relative(scanPath, path62.join(service.dir, relFile)));
|
|
15014
15147
|
const add = (edgeType, kind, name) => {
|
|
15015
15148
|
if (!name) return;
|
|
15016
15149
|
const result = emitPlatformResourceEdge(
|
|
@@ -15038,16 +15171,16 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
15038
15171
|
}
|
|
15039
15172
|
|
|
15040
15173
|
// src/extract/infra/railway.ts
|
|
15041
|
-
import { promises as
|
|
15042
|
-
import
|
|
15174
|
+
import { promises as fs31 } from "fs";
|
|
15175
|
+
import path63 from "path";
|
|
15043
15176
|
import { parse as parseToml4 } from "smol-toml";
|
|
15044
15177
|
import { EdgeType as EdgeType23 } from "@neat.is/types";
|
|
15045
15178
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
15046
15179
|
async function readRailwayConfig(dir) {
|
|
15047
15180
|
for (const filename of RAILWAY_FILENAMES) {
|
|
15048
|
-
const abs =
|
|
15181
|
+
const abs = path63.join(dir, filename);
|
|
15049
15182
|
if (!await exists(abs)) continue;
|
|
15050
|
-
const raw = await
|
|
15183
|
+
const raw = await fs31.readFile(abs, "utf8");
|
|
15051
15184
|
const config = filename === "railway.toml" ? parseToml4(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
15052
15185
|
return { config, relFile: filename, raw };
|
|
15053
15186
|
}
|
|
@@ -15061,7 +15194,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
15061
15194
|
try {
|
|
15062
15195
|
read = await readRailwayConfig(service.dir);
|
|
15063
15196
|
} catch (err) {
|
|
15064
|
-
recordExtractionError("infra railway",
|
|
15197
|
+
recordExtractionError("infra railway", path63.relative(scanPath, service.dir), err);
|
|
15065
15198
|
continue;
|
|
15066
15199
|
}
|
|
15067
15200
|
if (!read) continue;
|
|
@@ -15071,7 +15204,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
15071
15204
|
}
|
|
15072
15205
|
const anchorId = service.node.id;
|
|
15073
15206
|
const { config, relFile, raw } = read;
|
|
15074
|
-
const evidenceFile = toPosix(
|
|
15207
|
+
const evidenceFile = toPosix(path63.relative(scanPath, path63.join(service.dir, relFile)));
|
|
15075
15208
|
const add = (edgeType, kind, name) => {
|
|
15076
15209
|
if (!name) return;
|
|
15077
15210
|
const result = emitPlatformResourceEdge(
|
|
@@ -15095,15 +15228,15 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
15095
15228
|
}
|
|
15096
15229
|
|
|
15097
15230
|
// src/extract/infra/supabase.ts
|
|
15098
|
-
import { promises as
|
|
15099
|
-
import
|
|
15231
|
+
import { promises as fs32 } from "fs";
|
|
15232
|
+
import path64 from "path";
|
|
15100
15233
|
import { parse as parseToml5 } from "smol-toml";
|
|
15101
15234
|
import { EdgeType as EdgeType24 } from "@neat.is/types";
|
|
15102
15235
|
async function readSupabaseConfig(dir) {
|
|
15103
|
-
const relFile =
|
|
15104
|
-
const abs =
|
|
15236
|
+
const relFile = path64.join("supabase", "config.toml");
|
|
15237
|
+
const abs = path64.join(dir, relFile);
|
|
15105
15238
|
if (!await exists(abs)) return null;
|
|
15106
|
-
const raw = await
|
|
15239
|
+
const raw = await fs32.readFile(abs, "utf8");
|
|
15107
15240
|
const config = parseToml5(raw);
|
|
15108
15241
|
return { config, relFile, raw };
|
|
15109
15242
|
}
|
|
@@ -15115,7 +15248,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
15115
15248
|
try {
|
|
15116
15249
|
read = await readSupabaseConfig(service.dir);
|
|
15117
15250
|
} catch (err) {
|
|
15118
|
-
recordExtractionError("infra supabase",
|
|
15251
|
+
recordExtractionError("infra supabase", path64.relative(scanPath, service.dir), err);
|
|
15119
15252
|
continue;
|
|
15120
15253
|
}
|
|
15121
15254
|
if (!read) continue;
|
|
@@ -15130,7 +15263,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
15130
15263
|
});
|
|
15131
15264
|
}
|
|
15132
15265
|
const anchorId = service.node.id;
|
|
15133
|
-
const evidenceFile = toPosix(
|
|
15266
|
+
const evidenceFile = toPosix(path64.relative(scanPath, path64.join(service.dir, relFile)));
|
|
15134
15267
|
const add = (edgeType, kind, name) => {
|
|
15135
15268
|
if (!name) return;
|
|
15136
15269
|
const result = emitPlatformResourceEdge(
|
|
@@ -15171,7 +15304,7 @@ async function addInfra(graph, scanPath, services) {
|
|
|
15171
15304
|
}
|
|
15172
15305
|
|
|
15173
15306
|
// src/extract/zod-shapes.ts
|
|
15174
|
-
import
|
|
15307
|
+
import path65 from "path";
|
|
15175
15308
|
import Parser18 from "tree-sitter";
|
|
15176
15309
|
import JavaScript8 from "tree-sitter-javascript";
|
|
15177
15310
|
import {
|
|
@@ -15273,7 +15406,7 @@ function topLevelSchemas(root) {
|
|
|
15273
15406
|
}
|
|
15274
15407
|
function zodShapesFromFile(file, serviceDir) {
|
|
15275
15408
|
if (!ZOD_IMPORT_RE.test(file.content)) return [];
|
|
15276
|
-
const tree = parseSource3(parserForExt4(
|
|
15409
|
+
const tree = parseSource3(parserForExt4(path65.extname(file.path)), file.content);
|
|
15277
15410
|
const out = [];
|
|
15278
15411
|
const seen = /* @__PURE__ */ new Set();
|
|
15279
15412
|
for (const { name, call } of topLevelSchemas(tree.rootNode)) {
|
|
@@ -15291,7 +15424,7 @@ function zodShapesFromFile(file, serviceDir) {
|
|
|
15291
15424
|
name,
|
|
15292
15425
|
fields,
|
|
15293
15426
|
evidence: {
|
|
15294
|
-
file:
|
|
15427
|
+
file: path65.relative(serviceDir, file.path),
|
|
15295
15428
|
line,
|
|
15296
15429
|
snippet: snippet(file.content, line)
|
|
15297
15430
|
}
|
|
@@ -15558,11 +15691,11 @@ async function addFirestoreRules(graph, services) {
|
|
|
15558
15691
|
}
|
|
15559
15692
|
|
|
15560
15693
|
// src/extract/index.ts
|
|
15561
|
-
import
|
|
15694
|
+
import path67 from "path";
|
|
15562
15695
|
|
|
15563
15696
|
// src/extract/retire.ts
|
|
15564
15697
|
import { existsSync as existsSync2 } from "fs";
|
|
15565
|
-
import
|
|
15698
|
+
import path66 from "path";
|
|
15566
15699
|
import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
|
|
15567
15700
|
function dropOrphanedFileNodes(graph) {
|
|
15568
15701
|
const orphans = [];
|
|
@@ -15596,11 +15729,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
15596
15729
|
if (edge.provenance !== Provenance23.EXTRACTED) return;
|
|
15597
15730
|
const evidenceFile = edge.evidence?.file;
|
|
15598
15731
|
if (!evidenceFile) return;
|
|
15599
|
-
if (
|
|
15732
|
+
if (path66.isAbsolute(evidenceFile)) {
|
|
15600
15733
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
15601
15734
|
return;
|
|
15602
15735
|
}
|
|
15603
|
-
const found = bases.some((base) => existsSync2(
|
|
15736
|
+
const found = bases.some((base) => existsSync2(path66.join(base, evidenceFile)));
|
|
15604
15737
|
if (!found) toDrop.push(id);
|
|
15605
15738
|
});
|
|
15606
15739
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -15612,6 +15745,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
15612
15745
|
async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
15613
15746
|
await ensureCompatLoaded();
|
|
15614
15747
|
drainExtractionErrors();
|
|
15748
|
+
drainSkippedFiles();
|
|
15615
15749
|
const services = await discoverServices(scanPath);
|
|
15616
15750
|
const phase1Nodes = addServiceNodes(graph, services);
|
|
15617
15751
|
await addServiceAliases(graph, scanPath, services);
|
|
@@ -15656,8 +15790,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
15656
15790
|
}
|
|
15657
15791
|
}
|
|
15658
15792
|
const droppedEntries = drainDroppedExtracted();
|
|
15793
|
+
const skippedEntries = drainSkippedFiles();
|
|
15659
15794
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
15660
|
-
const rejectedPath =
|
|
15795
|
+
const rejectedPath = path67.join(path67.dirname(opts.errorsPath), "rejected.ndjson");
|
|
15661
15796
|
try {
|
|
15662
15797
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
15663
15798
|
} catch (err) {
|
|
@@ -15674,7 +15809,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
15674
15809
|
errorEntries,
|
|
15675
15810
|
ghostsRetired,
|
|
15676
15811
|
extractedDropped: droppedEntries.length,
|
|
15677
|
-
droppedEntries
|
|
15812
|
+
droppedEntries,
|
|
15813
|
+
skippedFiles: skippedEntries.length,
|
|
15814
|
+
skippedEntries
|
|
15678
15815
|
};
|
|
15679
15816
|
emitNeatEvent({
|
|
15680
15817
|
type: "extraction-complete",
|
|
@@ -16376,8 +16513,8 @@ function computeDivergences(graph, opts = {}) {
|
|
|
16376
16513
|
}
|
|
16377
16514
|
|
|
16378
16515
|
// src/persist.ts
|
|
16379
|
-
import { promises as
|
|
16380
|
-
import
|
|
16516
|
+
import { promises as fs33 } from "fs";
|
|
16517
|
+
import path68 from "path";
|
|
16381
16518
|
import { NodeType as NodeType31, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
16382
16519
|
var SCHEMA_VERSION = 7;
|
|
16383
16520
|
function migrateV1ToV2(payload) {
|
|
@@ -16432,7 +16569,43 @@ function migrateV2ToV3(payload) {
|
|
|
16432
16569
|
return { ...payload, schemaVersion: 3 };
|
|
16433
16570
|
}
|
|
16434
16571
|
async function ensureDir(filePath) {
|
|
16435
|
-
await
|
|
16572
|
+
await fs33.mkdir(path68.dirname(filePath), { recursive: true });
|
|
16573
|
+
}
|
|
16574
|
+
var WRITE_FLUSH_CHARS = 4 * 1024 * 1024;
|
|
16575
|
+
function jsonOrUndefined(value) {
|
|
16576
|
+
return JSON.stringify(value);
|
|
16577
|
+
}
|
|
16578
|
+
function* serializeArray(items) {
|
|
16579
|
+
yield "[";
|
|
16580
|
+
for (let i = 0; i < items.length; i++) {
|
|
16581
|
+
if (i > 0) yield ",";
|
|
16582
|
+
yield jsonOrUndefined(items[i]) ?? "null";
|
|
16583
|
+
}
|
|
16584
|
+
yield "]";
|
|
16585
|
+
}
|
|
16586
|
+
function isPlainObject(value) {
|
|
16587
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
16588
|
+
const proto = Object.getPrototypeOf(value);
|
|
16589
|
+
return proto === Object.prototype || proto === null;
|
|
16590
|
+
}
|
|
16591
|
+
function* serializeObject(obj) {
|
|
16592
|
+
yield "{";
|
|
16593
|
+
let first = true;
|
|
16594
|
+
for (const key of Object.keys(obj)) {
|
|
16595
|
+
const value = obj[key];
|
|
16596
|
+
const descend = Array.isArray(value) || isPlainObject(value);
|
|
16597
|
+
if (!descend && jsonOrUndefined(value) === void 0) continue;
|
|
16598
|
+
if (!first) yield ",";
|
|
16599
|
+
first = false;
|
|
16600
|
+
yield `${JSON.stringify(key)}:`;
|
|
16601
|
+
if (Array.isArray(value)) yield* serializeArray(value);
|
|
16602
|
+
else if (isPlainObject(value)) yield* serializeObject(value);
|
|
16603
|
+
else yield jsonOrUndefined(value);
|
|
16604
|
+
}
|
|
16605
|
+
yield "}";
|
|
16606
|
+
}
|
|
16607
|
+
function* serializeGraphChunks(payload) {
|
|
16608
|
+
yield* serializeObject(payload);
|
|
16436
16609
|
}
|
|
16437
16610
|
async function saveGraphToDisk(graph, outPath) {
|
|
16438
16611
|
await ensureDir(outPath);
|
|
@@ -16442,13 +16615,26 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
16442
16615
|
graph: graph.export()
|
|
16443
16616
|
};
|
|
16444
16617
|
const tmp = `${outPath}.tmp`;
|
|
16445
|
-
await
|
|
16446
|
-
|
|
16618
|
+
const handle = await fs33.open(tmp, "w");
|
|
16619
|
+
try {
|
|
16620
|
+
let buffered = "";
|
|
16621
|
+
for (const chunk of serializeGraphChunks(payload)) {
|
|
16622
|
+
buffered += chunk;
|
|
16623
|
+
if (buffered.length >= WRITE_FLUSH_CHARS) {
|
|
16624
|
+
await handle.write(buffered, null, "utf8");
|
|
16625
|
+
buffered = "";
|
|
16626
|
+
}
|
|
16627
|
+
}
|
|
16628
|
+
if (buffered.length > 0) await handle.write(buffered, null, "utf8");
|
|
16629
|
+
} finally {
|
|
16630
|
+
await handle.close();
|
|
16631
|
+
}
|
|
16632
|
+
await fs33.rename(tmp, outPath);
|
|
16447
16633
|
}
|
|
16448
16634
|
async function loadGraphFromDisk(graph, outPath) {
|
|
16449
16635
|
let raw;
|
|
16450
16636
|
try {
|
|
16451
|
-
raw = await
|
|
16637
|
+
raw = await fs33.readFile(outPath, "utf8");
|
|
16452
16638
|
} catch (err) {
|
|
16453
16639
|
if (err.code === "ENOENT") return;
|
|
16454
16640
|
throw err;
|
|
@@ -16521,7 +16707,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
16521
16707
|
}
|
|
16522
16708
|
|
|
16523
16709
|
// src/diff.ts
|
|
16524
|
-
import { promises as
|
|
16710
|
+
import { promises as fs34 } from "fs";
|
|
16525
16711
|
async function loadSnapshotForDiff(target) {
|
|
16526
16712
|
if (/^https?:\/\//i.test(target)) {
|
|
16527
16713
|
const res = await fetch(target);
|
|
@@ -16530,7 +16716,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
16530
16716
|
}
|
|
16531
16717
|
return await res.json();
|
|
16532
16718
|
}
|
|
16533
|
-
const raw = await
|
|
16719
|
+
const raw = await fs34.readFile(target, "utf8");
|
|
16534
16720
|
return JSON.parse(raw);
|
|
16535
16721
|
}
|
|
16536
16722
|
function indexEntries(entries) {
|
|
@@ -16597,23 +16783,23 @@ function canonicalJson(value) {
|
|
|
16597
16783
|
}
|
|
16598
16784
|
|
|
16599
16785
|
// src/projects.ts
|
|
16600
|
-
import
|
|
16786
|
+
import path69 from "path";
|
|
16601
16787
|
function pathsForProject(project, baseDir) {
|
|
16602
16788
|
if (project === DEFAULT_PROJECT) {
|
|
16603
16789
|
return {
|
|
16604
|
-
snapshotPath:
|
|
16605
|
-
errorsPath:
|
|
16606
|
-
staleEventsPath:
|
|
16607
|
-
embeddingsCachePath:
|
|
16608
|
-
policyViolationsPath:
|
|
16790
|
+
snapshotPath: path69.join(baseDir, "graph.json"),
|
|
16791
|
+
errorsPath: path69.join(baseDir, "errors.ndjson"),
|
|
16792
|
+
staleEventsPath: path69.join(baseDir, "stale-events.ndjson"),
|
|
16793
|
+
embeddingsCachePath: path69.join(baseDir, "embeddings.json"),
|
|
16794
|
+
policyViolationsPath: path69.join(baseDir, "policy-violations.ndjson")
|
|
16609
16795
|
};
|
|
16610
16796
|
}
|
|
16611
16797
|
return {
|
|
16612
|
-
snapshotPath:
|
|
16613
|
-
errorsPath:
|
|
16614
|
-
staleEventsPath:
|
|
16615
|
-
embeddingsCachePath:
|
|
16616
|
-
policyViolationsPath:
|
|
16798
|
+
snapshotPath: path69.join(baseDir, `${project}.json`),
|
|
16799
|
+
errorsPath: path69.join(baseDir, `errors.${project}.ndjson`),
|
|
16800
|
+
staleEventsPath: path69.join(baseDir, `stale-events.${project}.ndjson`),
|
|
16801
|
+
embeddingsCachePath: path69.join(baseDir, `embeddings.${project}.json`),
|
|
16802
|
+
policyViolationsPath: path69.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
16617
16803
|
};
|
|
16618
16804
|
}
|
|
16619
16805
|
var Projects = class {
|
|
@@ -16652,9 +16838,9 @@ function parseExtraProjects(raw) {
|
|
|
16652
16838
|
}
|
|
16653
16839
|
|
|
16654
16840
|
// src/registry.ts
|
|
16655
|
-
import { promises as
|
|
16841
|
+
import { promises as fs35 } from "fs";
|
|
16656
16842
|
import os2 from "os";
|
|
16657
|
-
import
|
|
16843
|
+
import path70 from "path";
|
|
16658
16844
|
import {
|
|
16659
16845
|
RegistryFileSchema
|
|
16660
16846
|
} from "@neat.is/types";
|
|
@@ -16662,20 +16848,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
16662
16848
|
var LOCK_RETRY_MS = 50;
|
|
16663
16849
|
function neatHome() {
|
|
16664
16850
|
const override = process.env.NEAT_HOME;
|
|
16665
|
-
if (override && override.length > 0) return
|
|
16666
|
-
return
|
|
16851
|
+
if (override && override.length > 0) return path70.resolve(override);
|
|
16852
|
+
return path70.join(os2.homedir(), ".neat");
|
|
16667
16853
|
}
|
|
16668
16854
|
function registryPath() {
|
|
16669
|
-
return
|
|
16855
|
+
return path70.join(neatHome(), "projects.json");
|
|
16670
16856
|
}
|
|
16671
16857
|
function registryLockPath() {
|
|
16672
|
-
return
|
|
16858
|
+
return path70.join(neatHome(), "projects.json.lock");
|
|
16673
16859
|
}
|
|
16674
16860
|
function daemonPidPath() {
|
|
16675
|
-
return
|
|
16861
|
+
return path70.join(neatHome(), "neatd.pid");
|
|
16676
16862
|
}
|
|
16677
16863
|
function daemonsDir() {
|
|
16678
|
-
return
|
|
16864
|
+
return path70.join(neatHome(), "daemons");
|
|
16679
16865
|
}
|
|
16680
16866
|
function isFiniteInt(v) {
|
|
16681
16867
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -16708,7 +16894,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
16708
16894
|
const dir = daemonsDir();
|
|
16709
16895
|
let names;
|
|
16710
16896
|
try {
|
|
16711
|
-
names = await
|
|
16897
|
+
names = await fs35.readdir(dir);
|
|
16712
16898
|
} catch (err) {
|
|
16713
16899
|
if (err.code === "ENOENT") return [];
|
|
16714
16900
|
throw err;
|
|
@@ -16716,10 +16902,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
16716
16902
|
const out = [];
|
|
16717
16903
|
for (const name of names) {
|
|
16718
16904
|
if (!name.endsWith(".json")) continue;
|
|
16719
|
-
const file =
|
|
16905
|
+
const file = path70.join(dir, name);
|
|
16720
16906
|
let raw;
|
|
16721
16907
|
try {
|
|
16722
|
-
raw = await
|
|
16908
|
+
raw = await fs35.readFile(file, "utf8");
|
|
16723
16909
|
} catch {
|
|
16724
16910
|
continue;
|
|
16725
16911
|
}
|
|
@@ -16732,7 +16918,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
16732
16918
|
return out;
|
|
16733
16919
|
}
|
|
16734
16920
|
async function removeDaemonRecord(source) {
|
|
16735
|
-
await
|
|
16921
|
+
await fs35.unlink(source).catch(() => {
|
|
16736
16922
|
});
|
|
16737
16923
|
}
|
|
16738
16924
|
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
@@ -16789,7 +16975,7 @@ function isPidAliveDefault(pid) {
|
|
|
16789
16975
|
}
|
|
16790
16976
|
async function readPidFile(file) {
|
|
16791
16977
|
try {
|
|
16792
|
-
const raw = await
|
|
16978
|
+
const raw = await fs35.readFile(file, "utf8");
|
|
16793
16979
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
16794
16980
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
16795
16981
|
} catch {
|
|
@@ -16815,14 +17001,12 @@ async function readLockPid(lockPath) {
|
|
|
16815
17001
|
async function classifyLockHolder(lockPath, probe = defaultLockHolderProbe) {
|
|
16816
17002
|
const lockPid = await readLockPid(lockPath);
|
|
16817
17003
|
const daemonPid = await probe.daemonPidFromFile();
|
|
16818
|
-
if (daemonPid !== void 0 && daemonPid !== process.pid && probe.isPidAlive(daemonPid) && // The lock already names the daemon, or the daemon answers on its port.
|
|
16819
|
-
// Either confirms a live daemon is in the picture (the second guards
|
|
16820
|
-
// against a stale pidfile whose PID got reused).
|
|
16821
|
-
(daemonPid === lockPid || await probe.daemonResponds())) {
|
|
16822
|
-
return { kind: "daemon", pid: daemonPid };
|
|
16823
|
-
}
|
|
16824
17004
|
if (lockPid !== void 0 && lockPid !== process.pid && probe.isPidAlive(lockPid)) {
|
|
16825
|
-
return { kind: "command", pid: lockPid };
|
|
17005
|
+
return { kind: lockPid === daemonPid ? "daemon" : "command", pid: lockPid };
|
|
17006
|
+
}
|
|
17007
|
+
if (daemonPid !== void 0 && daemonPid !== process.pid && probe.isPidAlive(daemonPid) && // Guards against a stale pidfile whose PID got reused.
|
|
17008
|
+
await probe.daemonResponds()) {
|
|
17009
|
+
return { kind: "daemon", pid: daemonPid };
|
|
16826
17010
|
}
|
|
16827
17011
|
return { kind: "stale" };
|
|
16828
17012
|
}
|
|
@@ -16833,36 +17017,36 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
16833
17017
|
case "command":
|
|
16834
17018
|
return `Another neat command (pid ${holder.pid}) is holding the registry lock. Wait for it to finish, or check \`ps\` if you're not sure what's running.`;
|
|
16835
17019
|
case "stale":
|
|
16836
|
-
return `
|
|
17020
|
+
return `timed out after ${timeoutMs}ms waiting for ${lockPath}. Another neat process is holding the lock; if no such process exists, remove the file by hand.`;
|
|
16837
17021
|
}
|
|
16838
17022
|
}
|
|
16839
17023
|
async function normalizeProjectPath(input) {
|
|
16840
|
-
const resolved =
|
|
17024
|
+
const resolved = path70.resolve(input);
|
|
16841
17025
|
try {
|
|
16842
|
-
return await
|
|
17026
|
+
return await fs35.realpath(resolved);
|
|
16843
17027
|
} catch {
|
|
16844
17028
|
return resolved;
|
|
16845
17029
|
}
|
|
16846
17030
|
}
|
|
16847
17031
|
async function writeAtomically(target, contents) {
|
|
16848
|
-
await
|
|
17032
|
+
await fs35.mkdir(path70.dirname(target), { recursive: true });
|
|
16849
17033
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
16850
|
-
const fd = await
|
|
17034
|
+
const fd = await fs35.open(tmp, "w");
|
|
16851
17035
|
try {
|
|
16852
17036
|
await fd.writeFile(contents, "utf8");
|
|
16853
17037
|
await fd.sync();
|
|
16854
17038
|
} finally {
|
|
16855
17039
|
await fd.close();
|
|
16856
17040
|
}
|
|
16857
|
-
await
|
|
17041
|
+
await fs35.rename(tmp, target);
|
|
16858
17042
|
}
|
|
16859
17043
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
16860
17044
|
const deadline = Date.now() + timeoutMs;
|
|
16861
|
-
await
|
|
17045
|
+
await fs35.mkdir(path70.dirname(lockPath), { recursive: true });
|
|
16862
17046
|
let probedHolder = false;
|
|
16863
17047
|
while (true) {
|
|
16864
17048
|
try {
|
|
16865
|
-
const fd = await
|
|
17049
|
+
const fd = await fs35.open(lockPath, "wx");
|
|
16866
17050
|
try {
|
|
16867
17051
|
await fd.writeFile(`${process.pid}
|
|
16868
17052
|
`, "utf8");
|
|
@@ -16876,18 +17060,36 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
16876
17060
|
if (!probedHolder) {
|
|
16877
17061
|
probedHolder = true;
|
|
16878
17062
|
const holder = await classifyLockHolder(lockPath, probe);
|
|
16879
|
-
if (holder.kind === "daemon")
|
|
17063
|
+
if (holder.kind === "daemon") {
|
|
17064
|
+
throw new RegistryLockError(lockHolderMessage(holder, lockPath, timeoutMs));
|
|
17065
|
+
}
|
|
17066
|
+
if (holder.kind === "stale" && await isReclaimableLock(lockPath)) {
|
|
17067
|
+
await fs35.unlink(lockPath).catch(() => {
|
|
17068
|
+
});
|
|
17069
|
+
continue;
|
|
17070
|
+
}
|
|
16880
17071
|
}
|
|
16881
17072
|
if (Date.now() >= deadline) {
|
|
16882
17073
|
const holder = await classifyLockHolder(lockPath, probe);
|
|
16883
|
-
throw new
|
|
17074
|
+
throw new RegistryLockError(lockHolderMessage(holder, lockPath, timeoutMs));
|
|
16884
17075
|
}
|
|
16885
17076
|
await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
|
|
16886
17077
|
}
|
|
16887
17078
|
}
|
|
16888
17079
|
}
|
|
17080
|
+
var LOCK_UNSTAMPED_GRACE_MS = 5e3;
|
|
17081
|
+
async function isReclaimableLock(lockPath) {
|
|
17082
|
+
const pid = await readLockPid(lockPath);
|
|
17083
|
+
if (pid !== void 0) return true;
|
|
17084
|
+
try {
|
|
17085
|
+
const stat = await fs35.stat(lockPath);
|
|
17086
|
+
return Date.now() - stat.mtimeMs > LOCK_UNSTAMPED_GRACE_MS;
|
|
17087
|
+
} catch {
|
|
17088
|
+
return false;
|
|
17089
|
+
}
|
|
17090
|
+
}
|
|
16889
17091
|
async function releaseLock(lockPath) {
|
|
16890
|
-
await
|
|
17092
|
+
await fs35.unlink(lockPath).catch(() => {
|
|
16891
17093
|
});
|
|
16892
17094
|
}
|
|
16893
17095
|
async function withLock(fn) {
|
|
@@ -16903,7 +17105,7 @@ async function readRegistry() {
|
|
|
16903
17105
|
const file = registryPath();
|
|
16904
17106
|
let raw;
|
|
16905
17107
|
try {
|
|
16906
|
-
raw = await
|
|
17108
|
+
raw = await fs35.readFile(file, "utf8");
|
|
16907
17109
|
} catch (err) {
|
|
16908
17110
|
if (err.code === "ENOENT") {
|
|
16909
17111
|
return { version: 1, projects: [] };
|
|
@@ -16917,10 +17119,22 @@ async function writeRegistry(reg) {
|
|
|
16917
17119
|
const validated = RegistryFileSchema.parse(reg);
|
|
16918
17120
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
16919
17121
|
}
|
|
16920
|
-
var
|
|
17122
|
+
var RegistryError = class extends Error {
|
|
17123
|
+
constructor(message) {
|
|
17124
|
+
super(message);
|
|
17125
|
+
this.name = "RegistryError";
|
|
17126
|
+
}
|
|
17127
|
+
};
|
|
17128
|
+
var RegistryLockError = class extends RegistryError {
|
|
17129
|
+
constructor(message) {
|
|
17130
|
+
super(message);
|
|
17131
|
+
this.name = "RegistryLockError";
|
|
17132
|
+
}
|
|
17133
|
+
};
|
|
17134
|
+
var ProjectNameCollisionError = class extends RegistryError {
|
|
16921
17135
|
projectName;
|
|
16922
17136
|
constructor(name) {
|
|
16923
|
-
super(`
|
|
17137
|
+
super(`a project named "${name}" is already registered`);
|
|
16924
17138
|
this.name = "ProjectNameCollisionError";
|
|
16925
17139
|
this.projectName = name;
|
|
16926
17140
|
}
|
|
@@ -16961,6 +17175,16 @@ async function getProject(name) {
|
|
|
16961
17175
|
const reg = await readRegistry();
|
|
16962
17176
|
return reg.projects.find((p) => p.name === name);
|
|
16963
17177
|
}
|
|
17178
|
+
async function findProjectByPath(dir) {
|
|
17179
|
+
const here = await normalizeProjectPath(dir);
|
|
17180
|
+
const { projects } = await readRegistry();
|
|
17181
|
+
let best;
|
|
17182
|
+
for (const entry of projects) {
|
|
17183
|
+
const inside = here === entry.path || here.startsWith(entry.path + path70.sep);
|
|
17184
|
+
if (inside && (!best || entry.path.length > best.path.length)) best = entry;
|
|
17185
|
+
}
|
|
17186
|
+
return best;
|
|
17187
|
+
}
|
|
16964
17188
|
async function listProjects() {
|
|
16965
17189
|
const reg = await readRegistry();
|
|
16966
17190
|
return reg.projects;
|
|
@@ -16969,7 +17193,7 @@ async function setStatus(name, status) {
|
|
|
16969
17193
|
return withLock(async () => {
|
|
16970
17194
|
const reg = await readRegistry();
|
|
16971
17195
|
const entry = reg.projects.find((p) => p.name === name);
|
|
16972
|
-
if (!entry) throw new
|
|
17196
|
+
if (!entry) throw new RegistryError(`no project named "${name}"`);
|
|
16973
17197
|
entry.status = status;
|
|
16974
17198
|
await writeRegistry(reg);
|
|
16975
17199
|
return entry;
|
|
@@ -17008,7 +17232,7 @@ function pruneTtlMs() {
|
|
|
17008
17232
|
}
|
|
17009
17233
|
async function statPathStatus(p) {
|
|
17010
17234
|
try {
|
|
17011
|
-
const stat = await
|
|
17235
|
+
const stat = await fs35.stat(p);
|
|
17012
17236
|
return stat.isDirectory() ? "present" : "unknown";
|
|
17013
17237
|
} catch (err) {
|
|
17014
17238
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -17053,14 +17277,14 @@ import cors from "@fastify/cors";
|
|
|
17053
17277
|
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
17054
17278
|
|
|
17055
17279
|
// src/extend/index.ts
|
|
17056
|
-
import { promises as
|
|
17057
|
-
import
|
|
17280
|
+
import { promises as fs37 } from "fs";
|
|
17281
|
+
import path72 from "path";
|
|
17058
17282
|
import os3 from "os";
|
|
17059
17283
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
17060
17284
|
|
|
17061
17285
|
// src/installers/package-manager.ts
|
|
17062
|
-
import { promises as
|
|
17063
|
-
import
|
|
17286
|
+
import { promises as fs36 } from "fs";
|
|
17287
|
+
import path71 from "path";
|
|
17064
17288
|
import { spawn } from "child_process";
|
|
17065
17289
|
var LOCKFILE_PRIORITY = [
|
|
17066
17290
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -17075,29 +17299,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
17075
17299
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
17076
17300
|
async function exists2(p) {
|
|
17077
17301
|
try {
|
|
17078
|
-
await
|
|
17302
|
+
await fs36.access(p);
|
|
17079
17303
|
return true;
|
|
17080
17304
|
} catch {
|
|
17081
17305
|
return false;
|
|
17082
17306
|
}
|
|
17083
17307
|
}
|
|
17084
17308
|
async function detectPackageManager(serviceDir) {
|
|
17085
|
-
let dir =
|
|
17309
|
+
let dir = path71.resolve(serviceDir);
|
|
17086
17310
|
const stops = /* @__PURE__ */ new Set();
|
|
17087
17311
|
for (let i = 0; i < 64; i++) {
|
|
17088
17312
|
if (stops.has(dir)) break;
|
|
17089
17313
|
stops.add(dir);
|
|
17090
17314
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
17091
|
-
const lockPath =
|
|
17315
|
+
const lockPath = path71.join(dir, candidate.lockfile);
|
|
17092
17316
|
if (await exists2(lockPath)) {
|
|
17093
17317
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
17094
17318
|
}
|
|
17095
17319
|
}
|
|
17096
|
-
const parent =
|
|
17320
|
+
const parent = path71.dirname(dir);
|
|
17097
17321
|
if (parent === dir) break;
|
|
17098
17322
|
dir = parent;
|
|
17099
17323
|
}
|
|
17100
|
-
return { pm: "npm", cwd:
|
|
17324
|
+
return { pm: "npm", cwd: path71.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
17101
17325
|
}
|
|
17102
17326
|
async function runPackageManagerInstall(cmd) {
|
|
17103
17327
|
return new Promise((resolve) => {
|
|
@@ -17139,15 +17363,15 @@ ${err.message}`
|
|
|
17139
17363
|
// src/extend/index.ts
|
|
17140
17364
|
async function fileExists2(p) {
|
|
17141
17365
|
try {
|
|
17142
|
-
await
|
|
17366
|
+
await fs37.access(p);
|
|
17143
17367
|
return true;
|
|
17144
17368
|
} catch {
|
|
17145
17369
|
return false;
|
|
17146
17370
|
}
|
|
17147
17371
|
}
|
|
17148
17372
|
async function readPackageJson(scanPath) {
|
|
17149
|
-
const pkgPath =
|
|
17150
|
-
const raw = await
|
|
17373
|
+
const pkgPath = path72.join(scanPath, "package.json");
|
|
17374
|
+
const raw = await fs37.readFile(pkgPath, "utf8");
|
|
17151
17375
|
return JSON.parse(raw);
|
|
17152
17376
|
}
|
|
17153
17377
|
var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -17161,15 +17385,15 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
17161
17385
|
async function findHookFiles(scanPath) {
|
|
17162
17386
|
const found = [];
|
|
17163
17387
|
const walk11 = async (dir) => {
|
|
17164
|
-
const entries = await
|
|
17388
|
+
const entries = await fs37.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
17165
17389
|
for (const entry of entries) {
|
|
17166
17390
|
if (entry.isDirectory()) {
|
|
17167
17391
|
if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
|
|
17168
|
-
await walk11(
|
|
17392
|
+
await walk11(path72.join(dir, entry.name));
|
|
17169
17393
|
} else if (entry.isFile()) {
|
|
17170
17394
|
if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
|
|
17171
|
-
const rel =
|
|
17172
|
-
found.push(rel.split(
|
|
17395
|
+
const rel = path72.relative(scanPath, path72.join(dir, entry.name));
|
|
17396
|
+
found.push(rel.split(path72.sep).join("/"));
|
|
17173
17397
|
}
|
|
17174
17398
|
}
|
|
17175
17399
|
}
|
|
@@ -17180,7 +17404,7 @@ async function findHookFiles(scanPath) {
|
|
|
17180
17404
|
async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
17181
17405
|
let fallback = null;
|
|
17182
17406
|
for (const file of hookFiles) {
|
|
17183
|
-
const content = await
|
|
17407
|
+
const content = await fs37.readFile(path72.join(scanPath, file), "utf8");
|
|
17184
17408
|
const patched = splicedContent(content, snippet2);
|
|
17185
17409
|
if (patched !== null) return { file, content, patched };
|
|
17186
17410
|
if (fallback === null) fallback = { file, content };
|
|
@@ -17188,12 +17412,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
|
17188
17412
|
return { file: fallback.file, content: fallback.content, patched: null };
|
|
17189
17413
|
}
|
|
17190
17414
|
function extendLogPath() {
|
|
17191
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
17415
|
+
return process.env.NEAT_EXTEND_LOG ?? path72.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
17192
17416
|
}
|
|
17193
17417
|
async function appendExtendLog(entry) {
|
|
17194
17418
|
const logPath = extendLogPath();
|
|
17195
|
-
await
|
|
17196
|
-
await
|
|
17419
|
+
await fs37.mkdir(path72.dirname(logPath), { recursive: true });
|
|
17420
|
+
await fs37.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
17197
17421
|
}
|
|
17198
17422
|
function splicedContent(fileContent, snippet2) {
|
|
17199
17423
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -17251,7 +17475,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
17251
17475
|
}
|
|
17252
17476
|
async function describeProjectInstrumentation(ctx) {
|
|
17253
17477
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
17254
|
-
const envNeat = await fileExists2(
|
|
17478
|
+
const envNeat = await fileExists2(path72.join(ctx.scanPath, ".env.neat"));
|
|
17255
17479
|
const registryInstrPackages = new Set(
|
|
17256
17480
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
17257
17481
|
);
|
|
@@ -17273,7 +17497,7 @@ async function applyExtension(ctx, args, options) {
|
|
|
17273
17497
|
);
|
|
17274
17498
|
}
|
|
17275
17499
|
for (const file of hookFiles) {
|
|
17276
|
-
const content = await
|
|
17500
|
+
const content = await fs37.readFile(path72.join(ctx.scanPath, file), "utf8");
|
|
17277
17501
|
if (content.includes(args.registration_snippet)) {
|
|
17278
17502
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
17279
17503
|
}
|
|
@@ -17285,18 +17509,18 @@ async function applyExtension(ctx, args, options) {
|
|
|
17285
17509
|
);
|
|
17286
17510
|
}
|
|
17287
17511
|
const primaryFile = primary.file;
|
|
17288
|
-
const primaryPath =
|
|
17512
|
+
const primaryPath = path72.join(ctx.scanPath, primaryFile);
|
|
17289
17513
|
const filesTouched = [];
|
|
17290
17514
|
const depsAdded = [];
|
|
17291
|
-
const pkgPath =
|
|
17515
|
+
const pkgPath = path72.join(ctx.scanPath, "package.json");
|
|
17292
17516
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
17293
17517
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
17294
17518
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
17295
|
-
await
|
|
17519
|
+
await fs37.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
17296
17520
|
filesTouched.push("package.json");
|
|
17297
17521
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
17298
17522
|
}
|
|
17299
|
-
await
|
|
17523
|
+
await fs37.writeFile(primaryPath, primary.patched, "utf8");
|
|
17300
17524
|
filesTouched.push(primaryFile);
|
|
17301
17525
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
17302
17526
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -17327,7 +17551,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
17327
17551
|
};
|
|
17328
17552
|
}
|
|
17329
17553
|
for (const file of hookFiles) {
|
|
17330
|
-
const content = await
|
|
17554
|
+
const content = await fs37.readFile(path72.join(ctx.scanPath, file), "utf8");
|
|
17331
17555
|
if (content.includes(args.registration_snippet)) {
|
|
17332
17556
|
return {
|
|
17333
17557
|
library: args.library,
|
|
@@ -17362,28 +17586,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
17362
17586
|
if (!await fileExists2(logPath)) {
|
|
17363
17587
|
return { undone: false, message: "no apply found for library" };
|
|
17364
17588
|
}
|
|
17365
|
-
const raw = await
|
|
17589
|
+
const raw = await fs37.readFile(logPath, "utf8");
|
|
17366
17590
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
17367
17591
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
17368
17592
|
if (!match) {
|
|
17369
17593
|
return { undone: false, message: "no apply found for library" };
|
|
17370
17594
|
}
|
|
17371
|
-
const pkgPath =
|
|
17595
|
+
const pkgPath = path72.join(ctx.scanPath, "package.json");
|
|
17372
17596
|
if (await fileExists2(pkgPath)) {
|
|
17373
17597
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
17374
17598
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
17375
17599
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
17376
17600
|
pkg.dependencies = rest;
|
|
17377
|
-
await
|
|
17601
|
+
await fs37.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
17378
17602
|
}
|
|
17379
17603
|
}
|
|
17380
17604
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
17381
17605
|
for (const file of hookFiles) {
|
|
17382
|
-
const filePath =
|
|
17383
|
-
const content = await
|
|
17606
|
+
const filePath = path72.join(ctx.scanPath, file);
|
|
17607
|
+
const content = await fs37.readFile(filePath, "utf8");
|
|
17384
17608
|
if (content.includes(match.registration_snippet)) {
|
|
17385
17609
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
17386
|
-
await
|
|
17610
|
+
await fs37.writeFile(filePath, filtered, "utf8");
|
|
17387
17611
|
break;
|
|
17388
17612
|
}
|
|
17389
17613
|
}
|
|
@@ -18399,8 +18623,8 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
18399
18623
|
|
|
18400
18624
|
// src/connectors-config.ts
|
|
18401
18625
|
import os4 from "os";
|
|
18402
|
-
import
|
|
18403
|
-
import { promises as
|
|
18626
|
+
import path73 from "path";
|
|
18627
|
+
import { promises as fs38 } from "fs";
|
|
18404
18628
|
var CONNECTORS_CONFIG_VERSION = 1;
|
|
18405
18629
|
var EnvRefUnsetError = class extends Error {
|
|
18406
18630
|
ref;
|
|
@@ -18414,17 +18638,17 @@ var EnvRefUnsetError = class extends Error {
|
|
|
18414
18638
|
};
|
|
18415
18639
|
function neatHome2() {
|
|
18416
18640
|
const override = process.env.NEAT_HOME;
|
|
18417
|
-
if (override && override.length > 0) return
|
|
18418
|
-
return
|
|
18641
|
+
if (override && override.length > 0) return path73.resolve(override);
|
|
18642
|
+
return path73.join(os4.homedir(), ".neat");
|
|
18419
18643
|
}
|
|
18420
18644
|
function connectorsConfigPath(home = neatHome2()) {
|
|
18421
|
-
return
|
|
18645
|
+
return path73.join(home, "connectors.json");
|
|
18422
18646
|
}
|
|
18423
18647
|
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
18424
18648
|
async function warnIfModeLooserThan0600(file) {
|
|
18425
18649
|
if (process.platform === "win32") return;
|
|
18426
18650
|
try {
|
|
18427
|
-
const stat = await
|
|
18651
|
+
const stat = await fs38.stat(file);
|
|
18428
18652
|
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
18429
18653
|
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
18430
18654
|
console.warn(
|
|
@@ -18438,7 +18662,7 @@ async function readConnectorsConfig(home = neatHome2()) {
|
|
|
18438
18662
|
const file = connectorsConfigPath(home);
|
|
18439
18663
|
let raw;
|
|
18440
18664
|
try {
|
|
18441
|
-
raw = await
|
|
18665
|
+
raw = await fs38.readFile(file, "utf8");
|
|
18442
18666
|
} catch (err) {
|
|
18443
18667
|
if (err.code === "ENOENT") {
|
|
18444
18668
|
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
@@ -18549,7 +18773,7 @@ function connectorMatchesProject(entry, project) {
|
|
|
18549
18773
|
var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
|
|
18550
18774
|
var CONNECTORS_LOCK_RETRY_MS = 50;
|
|
18551
18775
|
function connectorsConfigLockPath(home = neatHome2()) {
|
|
18552
|
-
return
|
|
18776
|
+
return path73.join(home, "connectors.json.lock");
|
|
18553
18777
|
}
|
|
18554
18778
|
function isEnvRef(value) {
|
|
18555
18779
|
return value.length > 1 && value.startsWith("$");
|
|
@@ -18562,9 +18786,9 @@ function redactCredentialRef(ref) {
|
|
|
18562
18786
|
return out;
|
|
18563
18787
|
}
|
|
18564
18788
|
async function writeConfigAtomically0600(file, contents) {
|
|
18565
|
-
await
|
|
18789
|
+
await fs38.mkdir(path73.dirname(file), { recursive: true });
|
|
18566
18790
|
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
18567
|
-
const fd = await
|
|
18791
|
+
const fd = await fs38.open(tmp, "w", 384);
|
|
18568
18792
|
try {
|
|
18569
18793
|
await fd.writeFile(contents, "utf8");
|
|
18570
18794
|
await fd.chmod(384);
|
|
@@ -18572,14 +18796,14 @@ async function writeConfigAtomically0600(file, contents) {
|
|
|
18572
18796
|
} finally {
|
|
18573
18797
|
await fd.close();
|
|
18574
18798
|
}
|
|
18575
|
-
await
|
|
18799
|
+
await fs38.rename(tmp, file);
|
|
18576
18800
|
}
|
|
18577
18801
|
async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
|
|
18578
18802
|
const deadline = Date.now() + timeoutMs;
|
|
18579
|
-
await
|
|
18803
|
+
await fs38.mkdir(path73.dirname(lockPath), { recursive: true });
|
|
18580
18804
|
for (; ; ) {
|
|
18581
18805
|
try {
|
|
18582
|
-
const fd = await
|
|
18806
|
+
const fd = await fs38.open(lockPath, "wx");
|
|
18583
18807
|
try {
|
|
18584
18808
|
await fd.writeFile(`${process.pid}
|
|
18585
18809
|
`, "utf8");
|
|
@@ -18599,7 +18823,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
|
|
|
18599
18823
|
}
|
|
18600
18824
|
}
|
|
18601
18825
|
async function releaseConnectorsLock(lockPath) {
|
|
18602
|
-
await
|
|
18826
|
+
await fs38.unlink(lockPath).catch(() => {
|
|
18603
18827
|
});
|
|
18604
18828
|
}
|
|
18605
18829
|
async function withConnectorsLock(home, fn) {
|
|
@@ -19339,10 +19563,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
19339
19563
|
// src/connectors/supabase/map.ts
|
|
19340
19564
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
19341
19565
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
19342
|
-
function targetFromRestPath(
|
|
19343
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
19566
|
+
function targetFromRestPath(path74) {
|
|
19567
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path74);
|
|
19344
19568
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
19345
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
19569
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path74);
|
|
19346
19570
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
19347
19571
|
return null;
|
|
19348
19572
|
}
|
|
@@ -19937,9 +20161,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
19937
20161
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
19938
20162
|
if (secondSep === -1) return null;
|
|
19939
20163
|
const method = rest.slice(0, secondSep);
|
|
19940
|
-
const
|
|
19941
|
-
if (!resourceName || !method || !
|
|
19942
|
-
return { resourceName, method, path:
|
|
20164
|
+
const path74 = rest.slice(secondSep + 1);
|
|
20165
|
+
if (!resourceName || !method || !path74) return null;
|
|
20166
|
+
return { resourceName, method, path: path74 };
|
|
19943
20167
|
}
|
|
19944
20168
|
function resourceNameFor(type, labels) {
|
|
19945
20169
|
if (!labels) return null;
|
|
@@ -19977,14 +20201,14 @@ function mapLogEntryToSignal(entry) {
|
|
|
19977
20201
|
if (!req) return null;
|
|
19978
20202
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
19979
20203
|
const method = req.requestMethod.toUpperCase();
|
|
19980
|
-
const
|
|
19981
|
-
if (
|
|
20204
|
+
const path74 = pathFromRequestUrl(req.requestUrl);
|
|
20205
|
+
if (path74 === null) return null;
|
|
19982
20206
|
const timestamp = entry.timestamp;
|
|
19983
20207
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
19984
20208
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
19985
20209
|
return {
|
|
19986
20210
|
targetKind: resourceType,
|
|
19987
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
20211
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path74 }),
|
|
19988
20212
|
callCount: 1,
|
|
19989
20213
|
errorCount: isError ? 1 : 0,
|
|
19990
20214
|
lastObservedIso: timestamp
|
|
@@ -20007,9 +20231,9 @@ import { NodeType as NodeType35 } from "@neat.is/types";
|
|
|
20007
20231
|
function normalizeName(name) {
|
|
20008
20232
|
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
20009
20233
|
}
|
|
20010
|
-
function inferServiceName(graph, resourceName, method,
|
|
20234
|
+
function inferServiceName(graph, resourceName, method, path74) {
|
|
20011
20235
|
const wantedMethod = method.toUpperCase();
|
|
20012
|
-
const wantedPath = normalizePathTemplate(
|
|
20236
|
+
const wantedPath = normalizePathTemplate(path74);
|
|
20013
20237
|
const services = [];
|
|
20014
20238
|
const owners = /* @__PURE__ */ new Set();
|
|
20015
20239
|
graph.forEachNode((_id, attrs) => {
|
|
@@ -20217,7 +20441,7 @@ function mapEventToSignal(event) {
|
|
|
20217
20441
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
20218
20442
|
const statusCode = metadata?.statusCode;
|
|
20219
20443
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
20220
|
-
const
|
|
20444
|
+
const path74 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
20221
20445
|
return {
|
|
20222
20446
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
20223
20447
|
targetName: scriptName,
|
|
@@ -20225,7 +20449,7 @@ function mapEventToSignal(event) {
|
|
|
20225
20449
|
errorCount: isError ? 1 : 0,
|
|
20226
20450
|
lastObservedIso: observedAt.toISOString(),
|
|
20227
20451
|
method,
|
|
20228
|
-
...
|
|
20452
|
+
...path74 ? { path: path74 } : {},
|
|
20229
20453
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
20230
20454
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
20231
20455
|
};
|
|
@@ -20271,8 +20495,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
20271
20495
|
});
|
|
20272
20496
|
return found;
|
|
20273
20497
|
}
|
|
20274
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
20275
|
-
const normalizedPath = normalizePathTemplate(
|
|
20498
|
+
function findMatchingRouteNode(graph, serviceName, method, path74) {
|
|
20499
|
+
const normalizedPath = normalizePathTemplate(path74);
|
|
20276
20500
|
let found = null;
|
|
20277
20501
|
graph.forEachNode((id, attrs) => {
|
|
20278
20502
|
if (found) return;
|
|
@@ -20289,10 +20513,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
20289
20513
|
return (signal) => {
|
|
20290
20514
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
20291
20515
|
const scriptName = signal.targetName;
|
|
20292
|
-
const { method, path:
|
|
20516
|
+
const { method, path: path74 } = signal;
|
|
20293
20517
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
20294
|
-
if (!method || !
|
|
20295
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
20518
|
+
if (!method || !path74) return wholeFileId;
|
|
20519
|
+
return findMatchingRouteNode(graph, serviceName, method, path74) ?? wholeFileId;
|
|
20296
20520
|
};
|
|
20297
20521
|
const mapping = config.workers?.[scriptName];
|
|
20298
20522
|
if (mapping) {
|
|
@@ -20622,9 +20846,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
20622
20846
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
20623
20847
|
if (secondSep === -1) return null;
|
|
20624
20848
|
const method = rest.slice(0, secondSep);
|
|
20625
|
-
const
|
|
20626
|
-
if (!serviceName || !method || !
|
|
20627
|
-
return { serviceName, method, path:
|
|
20849
|
+
const path74 = rest.slice(secondSep + 1);
|
|
20850
|
+
if (!serviceName || !method || !path74) return null;
|
|
20851
|
+
return { serviceName, method, path: path74 };
|
|
20628
20852
|
}
|
|
20629
20853
|
|
|
20630
20854
|
// src/connectors/cloud-run/map.ts
|
|
@@ -20653,14 +20877,14 @@ function mapLogEntryToSignal2(entry) {
|
|
|
20653
20877
|
if (!req) return null;
|
|
20654
20878
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
20655
20879
|
const method = req.requestMethod.toUpperCase();
|
|
20656
|
-
const
|
|
20657
|
-
if (
|
|
20880
|
+
const path74 = pathFromRequestUrl2(req.requestUrl);
|
|
20881
|
+
if (path74 === null) return null;
|
|
20658
20882
|
const timestamp = entry.timestamp;
|
|
20659
20883
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
20660
20884
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
20661
20885
|
return {
|
|
20662
20886
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
20663
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
20887
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path74 }),
|
|
20664
20888
|
callCount: 1,
|
|
20665
20889
|
errorCount: isError ? 1 : 0,
|
|
20666
20890
|
lastObservedIso: timestamp
|
|
@@ -20698,14 +20922,14 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
20698
20922
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
20699
20923
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
20700
20924
|
if (!identity) return null;
|
|
20701
|
-
const { serviceName: gcpServiceName, method, path:
|
|
20702
|
-
const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method,
|
|
20925
|
+
const { serviceName: gcpServiceName, method, path: path74 } = identity;
|
|
20926
|
+
const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method, path74) ?? void 0 : void 0);
|
|
20703
20927
|
if (mappedService) {
|
|
20704
20928
|
const routeNodeId = findMatchingRouteNode2(
|
|
20705
20929
|
graph,
|
|
20706
20930
|
mappedService,
|
|
20707
20931
|
method,
|
|
20708
|
-
normalizePathTemplate(
|
|
20932
|
+
normalizePathTemplate(path74)
|
|
20709
20933
|
);
|
|
20710
20934
|
if (routeNodeId) {
|
|
20711
20935
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType32.CALLS };
|
|
@@ -20833,9 +21057,9 @@ function parseGcpLbTargetName(targetName) {
|
|
|
20833
21057
|
const secondSep = rest.indexOf(FIELD_SEP3);
|
|
20834
21058
|
if (secondSep === -1) return null;
|
|
20835
21059
|
const method = rest.slice(0, secondSep);
|
|
20836
|
-
const
|
|
20837
|
-
if (!backendServiceName || !method || !
|
|
20838
|
-
return { backendServiceName, method, path:
|
|
21060
|
+
const path74 = rest.slice(secondSep + 1);
|
|
21061
|
+
if (!backendServiceName || !method || !path74) return null;
|
|
21062
|
+
return { backendServiceName, method, path: path74 };
|
|
20839
21063
|
}
|
|
20840
21064
|
|
|
20841
21065
|
// src/connectors/gcp-lb/map.ts
|
|
@@ -20864,14 +21088,14 @@ function mapLogEntryToSignal3(entry) {
|
|
|
20864
21088
|
if (!req) return null;
|
|
20865
21089
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
20866
21090
|
const method = req.requestMethod.toUpperCase();
|
|
20867
|
-
const
|
|
20868
|
-
if (
|
|
21091
|
+
const path74 = pathFromRequestUrl3(req.requestUrl);
|
|
21092
|
+
if (path74 === null) return null;
|
|
20869
21093
|
const timestamp = entry.timestamp;
|
|
20870
21094
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
20871
21095
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD5;
|
|
20872
21096
|
return {
|
|
20873
21097
|
targetKind: GCP_LB_TARGET_KIND,
|
|
20874
|
-
targetName: packGcpLbTargetName({ backendServiceName, method, path:
|
|
21098
|
+
targetName: packGcpLbTargetName({ backendServiceName, method, path: path74 }),
|
|
20875
21099
|
callCount: 1,
|
|
20876
21100
|
errorCount: isError ? 1 : 0,
|
|
20877
21101
|
lastObservedIso: timestamp
|
|
@@ -20909,14 +21133,14 @@ function createGcpLbResolveTarget(graph, config) {
|
|
|
20909
21133
|
if (signal.targetKind !== GCP_LB_TARGET_KIND) return null;
|
|
20910
21134
|
const identity = parseGcpLbTargetName(signal.targetName);
|
|
20911
21135
|
if (!identity) return null;
|
|
20912
|
-
const { backendServiceName, method, path:
|
|
20913
|
-
const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method,
|
|
21136
|
+
const { backendServiceName, method, path: path74 } = identity;
|
|
21137
|
+
const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method, path74) ?? void 0 : void 0);
|
|
20914
21138
|
if (mappedService) {
|
|
20915
21139
|
const routeNodeId = findMatchingRouteNode3(
|
|
20916
21140
|
graph,
|
|
20917
21141
|
mappedService,
|
|
20918
21142
|
method,
|
|
20919
|
-
normalizePathTemplate(
|
|
21143
|
+
normalizePathTemplate(path74)
|
|
20920
21144
|
);
|
|
20921
21145
|
if (routeNodeId) {
|
|
20922
21146
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType33.CALLS };
|
|
@@ -22300,16 +22524,18 @@ function serializeGraph(graph) {
|
|
|
22300
22524
|
});
|
|
22301
22525
|
return { nodes, edges };
|
|
22302
22526
|
}
|
|
22303
|
-
function projectFromReq(req, singleProject) {
|
|
22527
|
+
function projectFromReq(req, singleProject, registry) {
|
|
22304
22528
|
const params = req.params;
|
|
22305
22529
|
const named = params.project;
|
|
22306
22530
|
if (singleProject) {
|
|
22307
22531
|
return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
|
|
22308
22532
|
}
|
|
22309
|
-
|
|
22533
|
+
if (named !== void 0) return named;
|
|
22534
|
+
const hosted = registry?.list() ?? [];
|
|
22535
|
+
return hosted.length === 1 ? hosted[0] : DEFAULT_PROJECT;
|
|
22310
22536
|
}
|
|
22311
22537
|
function resolveProject(registry, req, reply, bootstrap, singleProject) {
|
|
22312
|
-
const name = projectFromReq(req, singleProject);
|
|
22538
|
+
const name = projectFromReq(req, singleProject, registry);
|
|
22313
22539
|
const ctx = registry.get(name);
|
|
22314
22540
|
if (!ctx) {
|
|
22315
22541
|
const phase = bootstrap?.status(name);
|
|
@@ -22321,10 +22547,13 @@ function resolveProject(registry, req, reply, bootstrap, singleProject) {
|
|
|
22321
22547
|
void reply.code(503).send({ ready: false, project: name, status: "broken" });
|
|
22322
22548
|
return null;
|
|
22323
22549
|
}
|
|
22550
|
+
const unnamed = req.params.project === void 0;
|
|
22551
|
+
const hosted = registry.list();
|
|
22552
|
+
const hint = unnamed && name === DEFAULT_PROJECT && hosted.length > 1 ? `This daemon hosts ${hosted.length} projects and none is named \`default\`: ${hosted.join(", ")}. Name one as /projects/<name>/\u2026 , or reach that project's own daemon.` : "This daemon does not host that project. GET /projects lists what it serves (see hostedHere).";
|
|
22324
22553
|
void reply.code(404).send({
|
|
22325
22554
|
error: "project not found",
|
|
22326
22555
|
project: name,
|
|
22327
|
-
hint
|
|
22556
|
+
hint
|
|
22328
22557
|
});
|
|
22329
22558
|
return null;
|
|
22330
22559
|
}
|
|
@@ -23221,6 +23450,7 @@ export {
|
|
|
23221
23450
|
PolicyViolationsLog,
|
|
23222
23451
|
isStrictExtractionEnabled,
|
|
23223
23452
|
formatExtractionBanner,
|
|
23453
|
+
formatSkippedBanner,
|
|
23224
23454
|
formatPrecisionFloorBanner,
|
|
23225
23455
|
addImports,
|
|
23226
23456
|
addRoutes,
|
|
@@ -23269,9 +23499,11 @@ export {
|
|
|
23269
23499
|
normalizeProjectPath,
|
|
23270
23500
|
writeAtomically,
|
|
23271
23501
|
readRegistry,
|
|
23502
|
+
RegistryError,
|
|
23272
23503
|
ProjectNameCollisionError,
|
|
23273
23504
|
addProject,
|
|
23274
23505
|
getProject,
|
|
23506
|
+
findProjectByPath,
|
|
23275
23507
|
listProjects,
|
|
23276
23508
|
setStatus,
|
|
23277
23509
|
touchLastSeen,
|
|
@@ -23298,4 +23530,4 @@ export {
|
|
|
23298
23530
|
deprovisionConnector,
|
|
23299
23531
|
buildApi
|
|
23300
23532
|
};
|
|
23301
|
-
//# sourceMappingURL=chunk-
|
|
23533
|
+
//# sourceMappingURL=chunk-LNIVRE4L.js.map
|