@neat.is/core 0.7.3 → 0.7.4
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-3SBEY67S.js → chunk-KN6O7KMK.js} +2 -2
- package/dist/{chunk-BY6ZP5WA.js → chunk-UU4XKSTO.js} +3734 -3418
- package/dist/chunk-UU4XKSTO.js.map +1 -0
- package/dist/cli.cjs +1861 -1533
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +18 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1192 -878
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +1192 -878
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +1256 -942
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-BY6ZP5WA.js.map +0 -1
- /package/dist/{chunk-3SBEY67S.js.map → chunk-KN6O7KMK.js.map} +0 -0
package/dist/server.cjs
CHANGED
|
@@ -1083,7 +1083,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
1083
1083
|
|
|
1084
1084
|
// src/divergences.ts
|
|
1085
1085
|
init_cjs_shims();
|
|
1086
|
-
var
|
|
1086
|
+
var import_types9 = require("@neat.is/types");
|
|
1087
1087
|
|
|
1088
1088
|
// src/columns.ts
|
|
1089
1089
|
init_cjs_shims();
|
|
@@ -1439,8 +1439,8 @@ init_cjs_shims();
|
|
|
1439
1439
|
|
|
1440
1440
|
// src/ingest.ts
|
|
1441
1441
|
init_cjs_shims();
|
|
1442
|
-
var
|
|
1443
|
-
var
|
|
1442
|
+
var import_node_fs9 = require("fs");
|
|
1443
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
1444
1444
|
var sourceMapJs = __toESM(require("source-map-js"), 1);
|
|
1445
1445
|
|
|
1446
1446
|
// src/policy.ts
|
|
@@ -1903,16 +1903,16 @@ var PolicyViolationsLog = class {
|
|
|
1903
1903
|
};
|
|
1904
1904
|
|
|
1905
1905
|
// src/ingest.ts
|
|
1906
|
-
var
|
|
1906
|
+
var import_types7 = require("@neat.is/types");
|
|
1907
1907
|
|
|
1908
1908
|
// src/extract/routes.ts
|
|
1909
1909
|
init_cjs_shims();
|
|
1910
|
-
var
|
|
1911
|
-
var
|
|
1912
|
-
var
|
|
1913
|
-
var
|
|
1914
|
-
var
|
|
1915
|
-
var
|
|
1910
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1911
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
1912
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
1913
|
+
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
1914
|
+
var import_tree_sitter_go2 = __toESM(require("tree-sitter-go"), 1);
|
|
1915
|
+
var import_types6 = require("@neat.is/types");
|
|
1916
1916
|
|
|
1917
1917
|
// src/extract/shared.ts
|
|
1918
1918
|
init_cjs_shims();
|
|
@@ -2283,7 +2283,15 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
|
2283
2283
|
return { fileNodeId, nodesAdded, edgesAdded };
|
|
2284
2284
|
}
|
|
2285
2285
|
|
|
2286
|
-
// src/extract/
|
|
2286
|
+
// src/extract/imports.ts
|
|
2287
|
+
init_cjs_shims();
|
|
2288
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
2289
|
+
var import_node_fs8 = require("fs");
|
|
2290
|
+
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
2291
|
+
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
2292
|
+
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
2293
|
+
var import_tree_sitter_go = __toESM(require("tree-sitter-go"), 1);
|
|
2294
|
+
var import_types5 = require("@neat.is/types");
|
|
2287
2295
|
var PARSE_CHUNK = 16384;
|
|
2288
2296
|
function parseSource(parser, source) {
|
|
2289
2297
|
return parser.parse(
|
|
@@ -2305,153 +2313,530 @@ function makeGoParser() {
|
|
|
2305
2313
|
p.setLanguage(import_tree_sitter_go.default);
|
|
2306
2314
|
return p;
|
|
2307
2315
|
}
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
"
|
|
2316
|
-
"all"
|
|
2317
|
-
]);
|
|
2318
|
-
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
2319
|
-
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
2320
|
-
function ginRoutesFromSource(source, parser) {
|
|
2321
|
-
const tree = parseSource(parser, source);
|
|
2322
|
-
const prefixes = /* @__PURE__ */ new Map();
|
|
2323
|
-
const out = [];
|
|
2324
|
-
walk(tree.rootNode, (node) => {
|
|
2325
|
-
if (node.type === "short_var_declaration" || node.type === "var_spec") {
|
|
2326
|
-
const name = node.childForFieldName("left")?.namedChild(0)?.text ?? node.childForFieldName("name")?.text;
|
|
2327
|
-
const value = node.childForFieldName("right")?.namedChild(0) ?? node.childForFieldName("value");
|
|
2328
|
-
if (name && value?.type === "call_expression") {
|
|
2329
|
-
const fn2 = value.childForFieldName("function");
|
|
2330
|
-
const field = fn2?.childForFieldName("field")?.text;
|
|
2331
|
-
const first2 = value.childForFieldName("arguments")?.namedChild(0);
|
|
2332
|
-
if (field === "Group" && first2?.type === "interpreted_string_literal") {
|
|
2333
|
-
prefixes.set(name, first2.text.slice(1, -1));
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
return;
|
|
2337
|
-
}
|
|
2338
|
-
if (node.type !== "call_expression") return;
|
|
2339
|
-
const fn = node.childForFieldName("function");
|
|
2340
|
-
if (fn?.type !== "selector_expression") return;
|
|
2341
|
-
const method = fn.childForFieldName("field")?.text?.toUpperCase();
|
|
2342
|
-
if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return;
|
|
2343
|
-
const receiver = fn.childForFieldName("operand")?.text ?? "";
|
|
2344
|
-
const first = node.childForFieldName("arguments")?.namedChild(0);
|
|
2345
|
-
if (first?.type !== "interpreted_string_literal") return;
|
|
2346
|
-
const leaf = first.text.slice(1, -1);
|
|
2347
|
-
out.push({
|
|
2348
|
-
method: method === "ALL" ? "ALL" : method,
|
|
2349
|
-
pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? "") + leaf),
|
|
2350
|
-
line: node.startPosition.row + 1,
|
|
2351
|
-
framework: "gin"
|
|
2352
|
-
});
|
|
2353
|
-
});
|
|
2354
|
-
return out;
|
|
2355
|
-
}
|
|
2356
|
-
var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
|
|
2357
|
-
var NESTJS_METHODS = /* @__PURE__ */ new Map([
|
|
2358
|
-
["Get", "GET"],
|
|
2359
|
-
["Post", "POST"],
|
|
2360
|
-
["Put", "PUT"],
|
|
2361
|
-
["Patch", "PATCH"],
|
|
2362
|
-
["Delete", "DELETE"],
|
|
2363
|
-
["Options", "OPTIONS"],
|
|
2364
|
-
["Head", "HEAD"],
|
|
2365
|
-
["All", "ALL"]
|
|
2366
|
-
]);
|
|
2367
|
-
function canonicalizeTemplate(raw) {
|
|
2368
|
-
let p = raw.split("?")[0].split("#")[0];
|
|
2369
|
-
if (!p.startsWith("/")) p = "/" + p;
|
|
2370
|
-
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
2371
|
-
return p;
|
|
2372
|
-
}
|
|
2373
|
-
function isDynamicSegment(seg) {
|
|
2374
|
-
if (seg.length === 0) return false;
|
|
2375
|
-
if (seg.includes(":")) return true;
|
|
2376
|
-
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
2377
|
-
if (/^\d+$/.test(seg)) return true;
|
|
2378
|
-
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg)) return true;
|
|
2379
|
-
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
2380
|
-
return false;
|
|
2316
|
+
function stringLiteralText(node) {
|
|
2317
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2318
|
+
const child = node.child(i);
|
|
2319
|
+
if (child?.type === "string_fragment") return child.text;
|
|
2320
|
+
}
|
|
2321
|
+
const raw = node.text;
|
|
2322
|
+
if (raw.length >= 2) return raw.slice(1, -1);
|
|
2323
|
+
return raw.length === 0 ? null : "";
|
|
2381
2324
|
}
|
|
2382
|
-
function
|
|
2383
|
-
const
|
|
2384
|
-
|
|
2385
|
-
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
2386
|
-
return "/" + normalised.join("/");
|
|
2325
|
+
function clipSnippet(text) {
|
|
2326
|
+
const oneLine = text.split("\n")[0] ?? text;
|
|
2327
|
+
return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
|
|
2387
2328
|
}
|
|
2388
|
-
function
|
|
2389
|
-
|
|
2329
|
+
function collectGoImports(node, out) {
|
|
2330
|
+
if (node.type === "import_spec") {
|
|
2331
|
+
const pathNode = node.childForFieldName("path");
|
|
2332
|
+
if (pathNode) {
|
|
2333
|
+
const specifier = pathNode.text.replace(/^`|`$/g, "").replace(/^"|"$/g, "");
|
|
2334
|
+
if (specifier) out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2335
|
+
}
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2390
2338
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2391
2339
|
const child = node.namedChild(i);
|
|
2392
|
-
if (child)
|
|
2340
|
+
if (child) collectGoImports(child, out);
|
|
2393
2341
|
}
|
|
2394
2342
|
}
|
|
2395
|
-
function
|
|
2396
|
-
if (node.type === "
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2343
|
+
function collectJsImports(node, out) {
|
|
2344
|
+
if (node.type === "import_statement") {
|
|
2345
|
+
const source = node.childForFieldName("source");
|
|
2346
|
+
if (source) {
|
|
2347
|
+
const specifier = stringLiteralText(source);
|
|
2348
|
+
if (specifier) {
|
|
2349
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2350
|
+
}
|
|
2400
2351
|
}
|
|
2401
|
-
return
|
|
2352
|
+
return;
|
|
2402
2353
|
}
|
|
2403
|
-
if (node.type === "
|
|
2404
|
-
|
|
2405
|
-
|
|
2354
|
+
if (node.type === "call_expression") {
|
|
2355
|
+
const fn = node.childForFieldName("function");
|
|
2356
|
+
if (fn?.type === "identifier" && fn.text === "require") {
|
|
2357
|
+
const args = node.childForFieldName("arguments");
|
|
2358
|
+
const firstArg = args?.namedChild(0);
|
|
2359
|
+
if (firstArg?.type === "string") {
|
|
2360
|
+
const specifier = stringLiteralText(firstArg);
|
|
2361
|
+
if (specifier) {
|
|
2362
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2406
2365
|
}
|
|
2407
|
-
const raw = node.text;
|
|
2408
|
-
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
2409
2366
|
}
|
|
2410
|
-
|
|
2367
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2368
|
+
const child = node.namedChild(i);
|
|
2369
|
+
if (child) collectJsImports(child, out);
|
|
2370
|
+
}
|
|
2411
2371
|
}
|
|
2412
|
-
function
|
|
2413
|
-
|
|
2414
|
-
const
|
|
2415
|
-
if (
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2372
|
+
function collectImportedNames(node, out) {
|
|
2373
|
+
if (node.type === "aliased_import") {
|
|
2374
|
+
const nameNode = node.childForFieldName("name");
|
|
2375
|
+
if (nameNode) out.push(nameNode.text);
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
if (node.type === "dotted_name") {
|
|
2379
|
+
out.push(node.text);
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2383
|
+
const child = node.namedChild(i);
|
|
2384
|
+
if (child) collectImportedNames(child, out);
|
|
2422
2385
|
}
|
|
2423
|
-
return null;
|
|
2424
2386
|
}
|
|
2425
|
-
function
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
const
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2387
|
+
function collectPyImports(node, out) {
|
|
2388
|
+
if (node.type === "import_from_statement") {
|
|
2389
|
+
let level = 0;
|
|
2390
|
+
let modulePath = "";
|
|
2391
|
+
const names = [];
|
|
2392
|
+
let pastFrom = false;
|
|
2393
|
+
let pastImport = false;
|
|
2394
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2395
|
+
const child = node.child(i);
|
|
2396
|
+
if (!child) continue;
|
|
2397
|
+
if (!pastFrom) {
|
|
2398
|
+
if (child.type === "from") pastFrom = true;
|
|
2399
|
+
continue;
|
|
2400
|
+
}
|
|
2401
|
+
if (!pastImport) {
|
|
2402
|
+
if (child.type === "import") {
|
|
2403
|
+
pastImport = true;
|
|
2404
|
+
continue;
|
|
2405
|
+
}
|
|
2406
|
+
if (child.type === "relative_import") {
|
|
2407
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
2408
|
+
const rc = child.child(j);
|
|
2409
|
+
if (!rc) continue;
|
|
2410
|
+
if (rc.type === "import_prefix") {
|
|
2411
|
+
for (let k = 0; k < rc.childCount; k++) {
|
|
2412
|
+
if (rc.child(k)?.type === ".") level++;
|
|
2413
|
+
}
|
|
2414
|
+
} else if (rc.type === "dotted_name") modulePath = rc.text;
|
|
2415
|
+
}
|
|
2416
|
+
} else if (child.type === "dotted_name") {
|
|
2417
|
+
modulePath = child.text;
|
|
2445
2418
|
}
|
|
2419
|
+
continue;
|
|
2446
2420
|
}
|
|
2447
|
-
|
|
2421
|
+
collectImportedNames(child, names);
|
|
2422
|
+
}
|
|
2423
|
+
if (level > 0 || modulePath) {
|
|
2424
|
+
out.push({ modulePath, level, names, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2448
2425
|
}
|
|
2449
2426
|
}
|
|
2450
|
-
|
|
2427
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2428
|
+
const child = node.namedChild(i);
|
|
2429
|
+
if (child) collectPyImports(child, out);
|
|
2430
|
+
}
|
|
2451
2431
|
}
|
|
2452
|
-
function
|
|
2453
|
-
|
|
2454
|
-
|
|
2432
|
+
async function fileExists2(p) {
|
|
2433
|
+
try {
|
|
2434
|
+
await import_node_fs8.promises.access(p);
|
|
2435
|
+
return true;
|
|
2436
|
+
} catch {
|
|
2437
|
+
return false;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
function isWithinServiceDir(candidate, serviceDir) {
|
|
2441
|
+
const rel = import_node_path8.default.relative(serviceDir, candidate);
|
|
2442
|
+
return rel !== "" && !rel.startsWith("..") && !import_node_path8.default.isAbsolute(rel);
|
|
2443
|
+
}
|
|
2444
|
+
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2445
|
+
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
2446
|
+
async function firstExistingCandidate(base, serviceDir) {
|
|
2447
|
+
for (const ext of JS_EXTENSIONS) {
|
|
2448
|
+
const candidate = base + ext;
|
|
2449
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
|
|
2450
|
+
return toPosix(import_node_path8.default.relative(serviceDir, candidate));
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
for (const indexFile of JS_INDEX_FILES) {
|
|
2454
|
+
const candidate = import_node_path8.default.join(base, indexFile);
|
|
2455
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
|
|
2456
|
+
return toPosix(import_node_path8.default.relative(serviceDir, candidate));
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
return null;
|
|
2460
|
+
}
|
|
2461
|
+
async function loadTsPathConfig(serviceDir) {
|
|
2462
|
+
const tsconfigPath = import_node_path8.default.join(serviceDir, "tsconfig.json");
|
|
2463
|
+
let raw;
|
|
2464
|
+
try {
|
|
2465
|
+
raw = await import_node_fs8.promises.readFile(tsconfigPath, "utf8");
|
|
2466
|
+
} catch {
|
|
2467
|
+
return null;
|
|
2468
|
+
}
|
|
2469
|
+
try {
|
|
2470
|
+
const parsed = JSON.parse(raw);
|
|
2471
|
+
const paths = parsed.compilerOptions?.paths;
|
|
2472
|
+
if (!paths || Object.keys(paths).length === 0) return null;
|
|
2473
|
+
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
2474
|
+
return { paths, baseDir: baseUrl ? import_node_path8.default.resolve(serviceDir, baseUrl) : serviceDir };
|
|
2475
|
+
} catch (err) {
|
|
2476
|
+
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
2477
|
+
return null;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
async function resolveTsAlias(specifier, config, serviceDir) {
|
|
2481
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
2482
|
+
let suffix = null;
|
|
2483
|
+
if (pattern === specifier) {
|
|
2484
|
+
suffix = "";
|
|
2485
|
+
} else if (pattern.endsWith("/*")) {
|
|
2486
|
+
const prefix = pattern.slice(0, -1);
|
|
2487
|
+
if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
|
|
2488
|
+
}
|
|
2489
|
+
if (suffix === null) continue;
|
|
2490
|
+
for (const target of targets) {
|
|
2491
|
+
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
2492
|
+
const resolvedBase = import_node_path8.default.resolve(config.baseDir, targetBase, suffix);
|
|
2493
|
+
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
2494
|
+
if (hit) return hit;
|
|
2495
|
+
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists2(resolvedBase)) {
|
|
2496
|
+
return toPosix(import_node_path8.default.relative(serviceDir, resolvedBase));
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
return null;
|
|
2501
|
+
}
|
|
2502
|
+
async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
|
|
2503
|
+
if (!specifier) return null;
|
|
2504
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
2505
|
+
const base = import_node_path8.default.resolve(importerDir, specifier);
|
|
2506
|
+
const ext = import_node_path8.default.extname(specifier);
|
|
2507
|
+
if (ext) {
|
|
2508
|
+
if (ext === ".js" || ext === ".jsx") {
|
|
2509
|
+
const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
|
|
2510
|
+
const tsSibling = base.slice(0, -ext.length) + tsExt;
|
|
2511
|
+
if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists2(tsSibling)) {
|
|
2512
|
+
return toPosix(import_node_path8.default.relative(serviceDir, tsSibling));
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
if (isWithinServiceDir(base, serviceDir) && await fileExists2(base)) {
|
|
2516
|
+
return toPosix(import_node_path8.default.relative(serviceDir, base));
|
|
2517
|
+
}
|
|
2518
|
+
if (!JS_EXTENSIONS.includes(ext)) {
|
|
2519
|
+
return firstExistingCandidate(base, serviceDir);
|
|
2520
|
+
}
|
|
2521
|
+
return null;
|
|
2522
|
+
}
|
|
2523
|
+
return firstExistingCandidate(base, serviceDir);
|
|
2524
|
+
}
|
|
2525
|
+
if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
|
|
2526
|
+
return null;
|
|
2527
|
+
}
|
|
2528
|
+
async function resolvePyImport(imp, importerPath, serviceDir) {
|
|
2529
|
+
let baseDir;
|
|
2530
|
+
if (imp.level > 0) {
|
|
2531
|
+
baseDir = import_node_path8.default.dirname(importerPath);
|
|
2532
|
+
for (let i = 1; i < imp.level; i++) baseDir = import_node_path8.default.dirname(baseDir);
|
|
2533
|
+
} else {
|
|
2534
|
+
baseDir = serviceDir;
|
|
2535
|
+
}
|
|
2536
|
+
const moduleBase = imp.modulePath ? import_node_path8.default.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
|
|
2537
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
2538
|
+
let needModuleFile = imp.names.length === 0;
|
|
2539
|
+
for (const name of imp.names) {
|
|
2540
|
+
const submoduleFile = import_node_path8.default.join(moduleBase, `${name}.py`);
|
|
2541
|
+
const subpackageInit = import_node_path8.default.join(moduleBase, name, "__init__.py");
|
|
2542
|
+
if (isWithinServiceDir(submoduleFile, serviceDir) && await fileExists2(submoduleFile)) {
|
|
2543
|
+
resolved.add(toPosix(import_node_path8.default.relative(serviceDir, submoduleFile)));
|
|
2544
|
+
} else if (isWithinServiceDir(subpackageInit, serviceDir) && await fileExists2(subpackageInit)) {
|
|
2545
|
+
resolved.add(toPosix(import_node_path8.default.relative(serviceDir, subpackageInit)));
|
|
2546
|
+
} else {
|
|
2547
|
+
needModuleFile = true;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
if (needModuleFile) {
|
|
2551
|
+
const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, import_node_path8.default.join(moduleBase, "__init__.py")] : [import_node_path8.default.join(moduleBase, "__init__.py")];
|
|
2552
|
+
for (const candidate of moduleFileCandidates) {
|
|
2553
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
|
|
2554
|
+
resolved.add(toPosix(import_node_path8.default.relative(serviceDir, candidate)));
|
|
2555
|
+
break;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
return [...resolved];
|
|
2560
|
+
}
|
|
2561
|
+
async function resolveGoImport(specifier, modulePath, serviceDir) {
|
|
2562
|
+
if (specifier !== modulePath && !specifier.startsWith(`${modulePath}/`)) return null;
|
|
2563
|
+
const suffix = specifier === modulePath ? "" : specifier.slice(modulePath.length + 1);
|
|
2564
|
+
const dir = import_node_path8.default.join(serviceDir, suffix);
|
|
2565
|
+
const entries = await import_node_fs8.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2566
|
+
const candidates = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".go") && !entry.name.endsWith("_test.go")).map((entry) => import_node_path8.default.join(dir, entry.name));
|
|
2567
|
+
if (candidates.length !== 1) return null;
|
|
2568
|
+
return toPosix(import_node_path8.default.relative(serviceDir, candidates[0]));
|
|
2569
|
+
}
|
|
2570
|
+
function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
|
|
2571
|
+
const importeeFileId = (0, import_types5.fileId)(serviceName, importeeRelPath);
|
|
2572
|
+
if (!graph.hasNode(importeeFileId)) return 0;
|
|
2573
|
+
const edgeId = (0, import_types5.extractedEdgeId)(importerFileId, importeeFileId, import_types5.EdgeType.IMPORTS);
|
|
2574
|
+
if (graph.hasEdge(edgeId)) return 0;
|
|
2575
|
+
const edge = {
|
|
2576
|
+
id: edgeId,
|
|
2577
|
+
source: importerFileId,
|
|
2578
|
+
target: importeeFileId,
|
|
2579
|
+
type: import_types5.EdgeType.IMPORTS,
|
|
2580
|
+
provenance: import_types5.Provenance.EXTRACTED,
|
|
2581
|
+
confidence: (0, import_types5.confidenceForExtracted)("structural"),
|
|
2582
|
+
evidence: { file: importerRelPath, line, snippet: snippet2 }
|
|
2583
|
+
};
|
|
2584
|
+
graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
|
|
2585
|
+
return 1;
|
|
2586
|
+
}
|
|
2587
|
+
async function addImports(graph, services) {
|
|
2588
|
+
const jsParser = makeJsParser();
|
|
2589
|
+
const pyParser = makePyParser();
|
|
2590
|
+
const goParser = makeGoParser();
|
|
2591
|
+
let edgesAdded = 0;
|
|
2592
|
+
for (const service of services) {
|
|
2593
|
+
const tsPaths = await loadTsPathConfig(service.dir);
|
|
2594
|
+
const files = await loadSourceFiles(service.dir);
|
|
2595
|
+
for (const file of files) {
|
|
2596
|
+
if (isTestPath(file.path)) continue;
|
|
2597
|
+
const relFile = toPosix(import_node_path8.default.relative(service.dir, file.path));
|
|
2598
|
+
const importerFileId = (0, import_types5.fileId)(service.pkg.name, relFile);
|
|
2599
|
+
const isPython = import_node_path8.default.extname(file.path) === ".py";
|
|
2600
|
+
const isGo = import_node_path8.default.extname(file.path) === ".go";
|
|
2601
|
+
if (isGo) {
|
|
2602
|
+
let goImports = [];
|
|
2603
|
+
try {
|
|
2604
|
+
const tree = parseSource(goParser, file.content);
|
|
2605
|
+
collectGoImports(tree.rootNode, goImports);
|
|
2606
|
+
} catch (err) {
|
|
2607
|
+
recordExtractionError("import extraction", file.path, err);
|
|
2608
|
+
continue;
|
|
2609
|
+
}
|
|
2610
|
+
const goMod = await import_node_fs8.promises.readFile(import_node_path8.default.join(service.dir, "go.mod"), "utf8").catch(() => "");
|
|
2611
|
+
const modulePath = goMod.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
|
|
2612
|
+
if (!modulePath) continue;
|
|
2613
|
+
for (const imp of goImports) {
|
|
2614
|
+
const resolved = await resolveGoImport(imp.specifier, modulePath, service.dir);
|
|
2615
|
+
if (!resolved) continue;
|
|
2616
|
+
edgesAdded += emitImportEdge(graph, service.pkg.name, importerFileId, relFile, resolved, imp.line, imp.snippet);
|
|
2617
|
+
}
|
|
2618
|
+
continue;
|
|
2619
|
+
}
|
|
2620
|
+
if (isPython) {
|
|
2621
|
+
let pyImports = [];
|
|
2622
|
+
try {
|
|
2623
|
+
const tree = parseSource(pyParser, file.content);
|
|
2624
|
+
collectPyImports(tree.rootNode, pyImports);
|
|
2625
|
+
} catch (err) {
|
|
2626
|
+
recordExtractionError("import extraction", file.path, err);
|
|
2627
|
+
continue;
|
|
2628
|
+
}
|
|
2629
|
+
for (const imp of pyImports) {
|
|
2630
|
+
const resolvedPaths = await resolvePyImport(imp, file.path, service.dir);
|
|
2631
|
+
for (const resolved of resolvedPaths) {
|
|
2632
|
+
edgesAdded += emitImportEdge(
|
|
2633
|
+
graph,
|
|
2634
|
+
service.pkg.name,
|
|
2635
|
+
importerFileId,
|
|
2636
|
+
relFile,
|
|
2637
|
+
resolved,
|
|
2638
|
+
imp.line,
|
|
2639
|
+
imp.snippet
|
|
2640
|
+
);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
continue;
|
|
2644
|
+
}
|
|
2645
|
+
let jsImports = [];
|
|
2646
|
+
try {
|
|
2647
|
+
const tree = parseSource(jsParser, file.content);
|
|
2648
|
+
collectJsImports(tree.rootNode, jsImports);
|
|
2649
|
+
} catch (err) {
|
|
2650
|
+
recordExtractionError("import extraction", file.path, err);
|
|
2651
|
+
continue;
|
|
2652
|
+
}
|
|
2653
|
+
for (const imp of jsImports) {
|
|
2654
|
+
const resolved = await resolveJsImport(imp.specifier, import_node_path8.default.dirname(file.path), service.dir, tsPaths);
|
|
2655
|
+
if (!resolved) continue;
|
|
2656
|
+
edgesAdded += emitImportEdge(
|
|
2657
|
+
graph,
|
|
2658
|
+
service.pkg.name,
|
|
2659
|
+
importerFileId,
|
|
2660
|
+
relFile,
|
|
2661
|
+
resolved,
|
|
2662
|
+
imp.line,
|
|
2663
|
+
imp.snippet
|
|
2664
|
+
);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
return { nodesAdded: 0, edgesAdded };
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
// src/extract/routes.ts
|
|
2672
|
+
var PARSE_CHUNK2 = 16384;
|
|
2673
|
+
function parseSource2(parser, source) {
|
|
2674
|
+
return parser.parse(
|
|
2675
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
2676
|
+
);
|
|
2677
|
+
}
|
|
2678
|
+
function makeJsParser2() {
|
|
2679
|
+
const p = new import_tree_sitter2.default();
|
|
2680
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
2681
|
+
return p;
|
|
2682
|
+
}
|
|
2683
|
+
function makePyParser2() {
|
|
2684
|
+
const p = new import_tree_sitter2.default();
|
|
2685
|
+
p.setLanguage(import_tree_sitter_python2.default);
|
|
2686
|
+
return p;
|
|
2687
|
+
}
|
|
2688
|
+
function makeGoParser2() {
|
|
2689
|
+
const p = new import_tree_sitter2.default();
|
|
2690
|
+
p.setLanguage(import_tree_sitter_go2.default);
|
|
2691
|
+
return p;
|
|
2692
|
+
}
|
|
2693
|
+
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
2694
|
+
"get",
|
|
2695
|
+
"post",
|
|
2696
|
+
"put",
|
|
2697
|
+
"patch",
|
|
2698
|
+
"delete",
|
|
2699
|
+
"options",
|
|
2700
|
+
"head",
|
|
2701
|
+
"all"
|
|
2702
|
+
]);
|
|
2703
|
+
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
2704
|
+
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
2705
|
+
function ginRoutesFromSource(source, parser) {
|
|
2706
|
+
const tree = parseSource2(parser, source);
|
|
2707
|
+
const prefixes = /* @__PURE__ */ new Map();
|
|
2708
|
+
const out = [];
|
|
2709
|
+
walk(tree.rootNode, (node) => {
|
|
2710
|
+
if (node.type === "short_var_declaration" || node.type === "var_spec") {
|
|
2711
|
+
const name = node.childForFieldName("left")?.namedChild(0)?.text ?? node.childForFieldName("name")?.text;
|
|
2712
|
+
const value = node.childForFieldName("right")?.namedChild(0) ?? node.childForFieldName("value");
|
|
2713
|
+
if (name && value?.type === "call_expression") {
|
|
2714
|
+
const fn2 = value.childForFieldName("function");
|
|
2715
|
+
const field = fn2?.childForFieldName("field")?.text;
|
|
2716
|
+
const first2 = value.childForFieldName("arguments")?.namedChild(0);
|
|
2717
|
+
if (field === "Group" && first2?.type === "interpreted_string_literal") {
|
|
2718
|
+
prefixes.set(name, first2.text.slice(1, -1));
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
return;
|
|
2722
|
+
}
|
|
2723
|
+
if (node.type !== "call_expression") return;
|
|
2724
|
+
const fn = node.childForFieldName("function");
|
|
2725
|
+
if (fn?.type !== "selector_expression") return;
|
|
2726
|
+
const method = fn.childForFieldName("field")?.text?.toUpperCase();
|
|
2727
|
+
if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return;
|
|
2728
|
+
const receiver = fn.childForFieldName("operand")?.text ?? "";
|
|
2729
|
+
const first = node.childForFieldName("arguments")?.namedChild(0);
|
|
2730
|
+
if (first?.type !== "interpreted_string_literal") return;
|
|
2731
|
+
const leaf = first.text.slice(1, -1);
|
|
2732
|
+
out.push({
|
|
2733
|
+
method: method === "ALL" ? "ALL" : method,
|
|
2734
|
+
pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? "") + leaf),
|
|
2735
|
+
line: node.startPosition.row + 1,
|
|
2736
|
+
framework: "gin"
|
|
2737
|
+
});
|
|
2738
|
+
});
|
|
2739
|
+
return out;
|
|
2740
|
+
}
|
|
2741
|
+
var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
|
|
2742
|
+
var NESTJS_METHODS = /* @__PURE__ */ new Map([
|
|
2743
|
+
["Get", "GET"],
|
|
2744
|
+
["Post", "POST"],
|
|
2745
|
+
["Put", "PUT"],
|
|
2746
|
+
["Patch", "PATCH"],
|
|
2747
|
+
["Delete", "DELETE"],
|
|
2748
|
+
["Options", "OPTIONS"],
|
|
2749
|
+
["Head", "HEAD"],
|
|
2750
|
+
["All", "ALL"]
|
|
2751
|
+
]);
|
|
2752
|
+
function canonicalizeTemplate(raw) {
|
|
2753
|
+
let p = raw.split("?")[0].split("#")[0];
|
|
2754
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
2755
|
+
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
2756
|
+
return p;
|
|
2757
|
+
}
|
|
2758
|
+
function isDynamicSegment(seg) {
|
|
2759
|
+
if (seg.length === 0) return false;
|
|
2760
|
+
if (seg.includes(":")) return true;
|
|
2761
|
+
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
2762
|
+
if (/^\d+$/.test(seg)) return true;
|
|
2763
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg)) return true;
|
|
2764
|
+
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
2765
|
+
return false;
|
|
2766
|
+
}
|
|
2767
|
+
function normalizePathTemplate(raw) {
|
|
2768
|
+
const canonical = canonicalizeTemplate(raw);
|
|
2769
|
+
const segments = canonical.split("/").filter((s) => s.length > 0);
|
|
2770
|
+
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
2771
|
+
return "/" + normalised.join("/");
|
|
2772
|
+
}
|
|
2773
|
+
function walk(node, visit) {
|
|
2774
|
+
visit(node);
|
|
2775
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2776
|
+
const child = node.namedChild(i);
|
|
2777
|
+
if (child) walk(child, visit);
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
function staticStringText(node) {
|
|
2781
|
+
if (node.type === "string") {
|
|
2782
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2783
|
+
const child = node.namedChild(i);
|
|
2784
|
+
if (child?.type === "string_fragment") return child.text;
|
|
2785
|
+
}
|
|
2786
|
+
return "";
|
|
2787
|
+
}
|
|
2788
|
+
if (node.type === "template_string") {
|
|
2789
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2790
|
+
if (node.namedChild(i)?.type === "template_substitution") return null;
|
|
2791
|
+
}
|
|
2792
|
+
const raw = node.text;
|
|
2793
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
2794
|
+
}
|
|
2795
|
+
return null;
|
|
2796
|
+
}
|
|
2797
|
+
function objectStringProp(objNode, key) {
|
|
2798
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
2799
|
+
const pair = objNode.namedChild(i);
|
|
2800
|
+
if (!pair || pair.type !== "pair") continue;
|
|
2801
|
+
const k = pair.childForFieldName("key");
|
|
2802
|
+
if (!k) continue;
|
|
2803
|
+
const kText = k.type === "string" ? staticStringText(k) : k.text;
|
|
2804
|
+
if (kText !== key) continue;
|
|
2805
|
+
const v = pair.childForFieldName("value");
|
|
2806
|
+
if (v) return staticStringText(v);
|
|
2807
|
+
}
|
|
2808
|
+
return null;
|
|
2809
|
+
}
|
|
2810
|
+
function fastifyRouteMethods(objNode) {
|
|
2811
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
2812
|
+
const pair = objNode.namedChild(i);
|
|
2813
|
+
if (!pair || pair.type !== "pair") continue;
|
|
2814
|
+
const k = pair.childForFieldName("key");
|
|
2815
|
+
const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
|
|
2816
|
+
if (kText !== "method") continue;
|
|
2817
|
+
const v = pair.childForFieldName("value");
|
|
2818
|
+
if (!v) return [];
|
|
2819
|
+
if (v.type === "string" || v.type === "template_string") {
|
|
2820
|
+
const s = staticStringText(v);
|
|
2821
|
+
return s ? [s.toUpperCase()] : [];
|
|
2822
|
+
}
|
|
2823
|
+
if (v.type === "array") {
|
|
2824
|
+
const out = [];
|
|
2825
|
+
for (let j = 0; j < v.namedChildCount; j++) {
|
|
2826
|
+
const el = v.namedChild(j);
|
|
2827
|
+
if (el && (el.type === "string" || el.type === "template_string")) {
|
|
2828
|
+
const s = staticStringText(el);
|
|
2829
|
+
if (s) out.push(s.toUpperCase());
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
return out;
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
return [];
|
|
2836
|
+
}
|
|
2837
|
+
function nestDecoratorImports(root) {
|
|
2838
|
+
const imports = /* @__PURE__ */ new Map();
|
|
2839
|
+
walk(root, (node) => {
|
|
2455
2840
|
if (node.type !== "import_statement") return;
|
|
2456
2841
|
const source = node.childForFieldName("source");
|
|
2457
2842
|
if (!source || staticStringText(source) !== "@nestjs/common") return;
|
|
@@ -2499,7 +2884,7 @@ function nestJoinedPath(prefix, leaf) {
|
|
|
2499
2884
|
return canonicalizeTemplate(segments.join("/"));
|
|
2500
2885
|
}
|
|
2501
2886
|
function nestjsRoutesFromSource(source, parser) {
|
|
2502
|
-
const tree =
|
|
2887
|
+
const tree = parseSource2(parser, source);
|
|
2503
2888
|
const imports = nestDecoratorImports(tree.rootNode);
|
|
2504
2889
|
if (![...imports.values()].includes("Controller")) return [];
|
|
2505
2890
|
const out = [];
|
|
@@ -2547,7 +2932,7 @@ function nestjsRoutesFromSource(source, parser) {
|
|
|
2547
2932
|
return out;
|
|
2548
2933
|
}
|
|
2549
2934
|
function serverRoutesFromSource(source, parser, hasExpress, hasFastify, hasHono = false) {
|
|
2550
|
-
const tree =
|
|
2935
|
+
const tree = parseSource2(parser, source);
|
|
2551
2936
|
const out = [];
|
|
2552
2937
|
const framework = hasExpress ? "express" : hasFastify ? "fastify" : hasHono ? "hono" : "unknown";
|
|
2553
2938
|
walk(tree.rootNode, (node) => {
|
|
@@ -2605,7 +2990,7 @@ function isNextPagesApiFile(relFile) {
|
|
|
2605
2990
|
if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
|
|
2606
2991
|
const base = segs[segs.length - 1] ?? "";
|
|
2607
2992
|
if (/^_(app|document|middleware)\./.test(base)) return false;
|
|
2608
|
-
return JS_ROUTE_EXTENSIONS.has(
|
|
2993
|
+
return JS_ROUTE_EXTENSIONS.has(import_node_path9.default.extname(base));
|
|
2609
2994
|
}
|
|
2610
2995
|
function nextSegment(seg) {
|
|
2611
2996
|
if (seg.startsWith("(") && seg.endsWith(")")) return null;
|
|
@@ -2667,7 +3052,7 @@ function nextAppMethods(root) {
|
|
|
2667
3052
|
}
|
|
2668
3053
|
function nextRoutesFromFile(source, relFile, parser) {
|
|
2669
3054
|
if (isNextAppRouteFile(relFile)) {
|
|
2670
|
-
const tree =
|
|
3055
|
+
const tree = parseSource2(parser, source);
|
|
2671
3056
|
const template = nextAppPathTemplate(relFile);
|
|
2672
3057
|
return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
|
|
2673
3058
|
method,
|
|
@@ -2788,7 +3173,7 @@ function collectMountPrefixes(root, consts) {
|
|
|
2788
3173
|
return mounts;
|
|
2789
3174
|
}
|
|
2790
3175
|
function pythonRoutesFromSource(source, parser, framework) {
|
|
2791
|
-
const tree =
|
|
3176
|
+
const tree = parseSource2(parser, source);
|
|
2792
3177
|
const prefixes = collectPythonRouterPrefixes(tree.rootNode);
|
|
2793
3178
|
const consts = collectStringConstants(tree.rootNode);
|
|
2794
3179
|
const mounts = collectMountPrefixes(tree.rootNode, consts);
|
|
@@ -2828,7 +3213,7 @@ function pythonRoutesFromSource(source, parser, framework) {
|
|
|
2828
3213
|
return out;
|
|
2829
3214
|
}
|
|
2830
3215
|
function djangoRoutesFromSource(source, parser) {
|
|
2831
|
-
const tree =
|
|
3216
|
+
const tree = parseSource2(parser, source);
|
|
2832
3217
|
const out = [];
|
|
2833
3218
|
walk(tree.rootNode, (node) => {
|
|
2834
3219
|
if (node.type !== "assignment") return;
|
|
@@ -2856,10 +3241,320 @@ function djangoRoutesFromSource(source, parser) {
|
|
|
2856
3241
|
});
|
|
2857
3242
|
return out;
|
|
2858
3243
|
}
|
|
3244
|
+
function namedArgs(argsNode) {
|
|
3245
|
+
const out = [];
|
|
3246
|
+
if (!argsNode) return out;
|
|
3247
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
3248
|
+
const c = argsNode.namedChild(i);
|
|
3249
|
+
if (c && c.type !== "comment") out.push(c);
|
|
3250
|
+
}
|
|
3251
|
+
return out;
|
|
3252
|
+
}
|
|
3253
|
+
function parseUseMount(callNode) {
|
|
3254
|
+
const args = namedArgs(callNode.childForFieldName("arguments"));
|
|
3255
|
+
if (args.length === 0) return null;
|
|
3256
|
+
const first = args[0];
|
|
3257
|
+
const firstStr = first.type === "string" || first.type === "template_string" ? staticStringText(first) : null;
|
|
3258
|
+
if (firstStr !== null && firstStr.startsWith("/")) {
|
|
3259
|
+
const prefix = canonicalizeTemplate(firstStr);
|
|
3260
|
+
const second = args[1];
|
|
3261
|
+
const target = second && second.type === "identifier" ? second.text : null;
|
|
3262
|
+
return { prefix: prefix === "/" ? "" : prefix, target };
|
|
3263
|
+
}
|
|
3264
|
+
if (args.length === 1 && first.type === "identifier") return { prefix: "", target: first.text };
|
|
3265
|
+
return null;
|
|
3266
|
+
}
|
|
3267
|
+
function unwrapRouterExpr(node, expressLocals, routerCtors) {
|
|
3268
|
+
if (node.type === "identifier") return { base: { alias: node.text }, mounts: [] };
|
|
3269
|
+
if (node.type !== "call_expression") return null;
|
|
3270
|
+
const fn = node.childForFieldName("function");
|
|
3271
|
+
if (!fn) return null;
|
|
3272
|
+
if (fn.type === "member_expression") {
|
|
3273
|
+
const prop = fn.childForFieldName("property")?.text;
|
|
3274
|
+
const obj = fn.childForFieldName("object");
|
|
3275
|
+
if (!prop || !obj) return null;
|
|
3276
|
+
if (prop === "use") {
|
|
3277
|
+
const inner = unwrapRouterExpr(obj, expressLocals, routerCtors);
|
|
3278
|
+
if (!inner) return null;
|
|
3279
|
+
const mount = parseUseMount(node);
|
|
3280
|
+
return { base: inner.base, mounts: mount ? [...inner.mounts, mount] : inner.mounts };
|
|
3281
|
+
}
|
|
3282
|
+
if (prop === "Router" && obj.type === "identifier" && expressLocals.has(obj.text)) {
|
|
3283
|
+
return { base: "newRouter", mounts: [] };
|
|
3284
|
+
}
|
|
3285
|
+
return null;
|
|
3286
|
+
}
|
|
3287
|
+
if (fn.type === "identifier") {
|
|
3288
|
+
if (expressLocals.has(fn.text)) return { base: "app", mounts: [] };
|
|
3289
|
+
if (routerCtors.has(fn.text)) return { base: "newRouter", mounts: [] };
|
|
3290
|
+
}
|
|
3291
|
+
return null;
|
|
3292
|
+
}
|
|
3293
|
+
function collectExpressImports(root) {
|
|
3294
|
+
const expressLocals = /* @__PURE__ */ new Set();
|
|
3295
|
+
const routerCtors = /* @__PURE__ */ new Set();
|
|
3296
|
+
const bindings = [];
|
|
3297
|
+
const addFromExpress = (local, sel, exported) => {
|
|
3298
|
+
if (sel === "default" || sel === "namespace") expressLocals.add(local);
|
|
3299
|
+
else if (exported === "Router") routerCtors.add(local);
|
|
3300
|
+
};
|
|
3301
|
+
walk(root, (node) => {
|
|
3302
|
+
if (node.type === "import_statement") {
|
|
3303
|
+
const source = node.childForFieldName("source");
|
|
3304
|
+
const spec = source ? staticStringText(source) : null;
|
|
3305
|
+
if (!spec) return;
|
|
3306
|
+
let clause = null;
|
|
3307
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3308
|
+
const c = node.namedChild(i);
|
|
3309
|
+
if (c?.type === "import_clause") clause = c;
|
|
3310
|
+
}
|
|
3311
|
+
if (!clause) return;
|
|
3312
|
+
for (let i = 0; i < clause.namedChildCount; i++) {
|
|
3313
|
+
const c = clause.namedChild(i);
|
|
3314
|
+
if (!c) continue;
|
|
3315
|
+
if (c.type === "identifier") {
|
|
3316
|
+
if (spec === "express") addFromExpress(c.text, "default", "default");
|
|
3317
|
+
else bindings.push({ local: c.text, specifier: spec, sel: "default" });
|
|
3318
|
+
} else if (c.type === "namespace_import") {
|
|
3319
|
+
const id = c.namedChild(0);
|
|
3320
|
+
if (id?.type === "identifier") {
|
|
3321
|
+
if (spec === "express") addFromExpress(id.text, "namespace", "namespace");
|
|
3322
|
+
else bindings.push({ local: id.text, specifier: spec, sel: "namespace" });
|
|
3323
|
+
}
|
|
3324
|
+
} else if (c.type === "named_imports") {
|
|
3325
|
+
for (let j = 0; j < c.namedChildCount; j++) {
|
|
3326
|
+
const s = c.namedChild(j);
|
|
3327
|
+
if (s?.type !== "import_specifier") continue;
|
|
3328
|
+
const name = s.childForFieldName("name")?.text;
|
|
3329
|
+
if (!name) continue;
|
|
3330
|
+
const local = s.childForFieldName("alias")?.text ?? name;
|
|
3331
|
+
if (spec === "express") addFromExpress(local, name, name);
|
|
3332
|
+
else bindings.push({ local, specifier: spec, sel: name });
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
if (node.type === "variable_declarator") {
|
|
3339
|
+
const value = node.childForFieldName("value");
|
|
3340
|
+
if (value?.type !== "call_expression") return;
|
|
3341
|
+
const fn = value.childForFieldName("function");
|
|
3342
|
+
if (fn?.type !== "identifier" || fn.text !== "require") return;
|
|
3343
|
+
const arg = namedArgs(value.childForFieldName("arguments"))[0];
|
|
3344
|
+
const spec = arg ? staticStringText(arg) : null;
|
|
3345
|
+
if (!spec) return;
|
|
3346
|
+
const name = node.childForFieldName("name");
|
|
3347
|
+
if (name?.type === "identifier") {
|
|
3348
|
+
if (spec === "express") expressLocals.add(name.text);
|
|
3349
|
+
else bindings.push({ local: name.text, specifier: spec, sel: "default" });
|
|
3350
|
+
} else if (name?.type === "object_pattern") {
|
|
3351
|
+
for (let i = 0; i < name.namedChildCount; i++) {
|
|
3352
|
+
const el = name.namedChild(i);
|
|
3353
|
+
if (!el) continue;
|
|
3354
|
+
let local;
|
|
3355
|
+
let exported;
|
|
3356
|
+
if (el.type === "shorthand_property_identifier_pattern") {
|
|
3357
|
+
local = el.text;
|
|
3358
|
+
exported = el.text;
|
|
3359
|
+
} else if (el.type === "pair_pattern") {
|
|
3360
|
+
exported = el.childForFieldName("key")?.text;
|
|
3361
|
+
local = el.childForFieldName("value")?.text ?? exported;
|
|
3362
|
+
}
|
|
3363
|
+
if (!local || !exported) continue;
|
|
3364
|
+
if (spec === "express") addFromExpress(local, exported, exported);
|
|
3365
|
+
else bindings.push({ local, specifier: spec, sel: exported });
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
});
|
|
3370
|
+
return { expressLocals, routerCtors, bindings };
|
|
3371
|
+
}
|
|
3372
|
+
function analyzeExpressFile(root, dir) {
|
|
3373
|
+
const { expressLocals, routerCtors, bindings } = collectExpressImports(root);
|
|
3374
|
+
const routerVars = /* @__PURE__ */ new Map();
|
|
3375
|
+
const appVars = /* @__PURE__ */ new Set();
|
|
3376
|
+
const exportNamed = /* @__PURE__ */ new Map();
|
|
3377
|
+
let exportDefaultName = null;
|
|
3378
|
+
const getVar = (name) => {
|
|
3379
|
+
let rv = routerVars.get(name);
|
|
3380
|
+
if (!rv) {
|
|
3381
|
+
rv = { declares: false, mounts: [] };
|
|
3382
|
+
routerVars.set(name, rv);
|
|
3383
|
+
}
|
|
3384
|
+
return rv;
|
|
3385
|
+
};
|
|
3386
|
+
const refFromExpr = (expr, key) => {
|
|
3387
|
+
if (expr.type === "identifier") return expr.text;
|
|
3388
|
+
const u = unwrapRouterExpr(expr, expressLocals, routerCtors);
|
|
3389
|
+
if (!u) return null;
|
|
3390
|
+
const rv = getVar(key);
|
|
3391
|
+
for (const m of u.mounts) rv.mounts.push(m);
|
|
3392
|
+
if (typeof u.base === "object") rv.aliasOf = u.base.alias;
|
|
3393
|
+
return key;
|
|
3394
|
+
};
|
|
3395
|
+
const isExported = (declarator) => {
|
|
3396
|
+
const decl = declarator.parent;
|
|
3397
|
+
return decl?.parent?.type === "export_statement";
|
|
3398
|
+
};
|
|
3399
|
+
walk(root, (node) => {
|
|
3400
|
+
if (node.type === "variable_declarator") {
|
|
3401
|
+
const value = node.childForFieldName("value");
|
|
3402
|
+
const name = node.childForFieldName("name");
|
|
3403
|
+
if (name?.type !== "identifier" || !value) return;
|
|
3404
|
+
if (value.type === "call_expression") {
|
|
3405
|
+
const fn = value.childForFieldName("function");
|
|
3406
|
+
if (fn?.type === "identifier" && fn.text === "require") return;
|
|
3407
|
+
}
|
|
3408
|
+
const u = unwrapRouterExpr(value, expressLocals, routerCtors);
|
|
3409
|
+
if (!u) return;
|
|
3410
|
+
const rv = getVar(name.text);
|
|
3411
|
+
for (const m of u.mounts) rv.mounts.push(m);
|
|
3412
|
+
if (u.base === "app") appVars.add(name.text);
|
|
3413
|
+
else if (typeof u.base === "object") rv.aliasOf = u.base.alias;
|
|
3414
|
+
if (isExported(node)) exportNamed.set(name.text, name.text);
|
|
3415
|
+
return;
|
|
3416
|
+
}
|
|
3417
|
+
if (node.type === "call_expression") {
|
|
3418
|
+
const fn = node.childForFieldName("function");
|
|
3419
|
+
if (fn?.type !== "member_expression") return;
|
|
3420
|
+
const obj = fn.childForFieldName("object");
|
|
3421
|
+
const prop = fn.childForFieldName("property")?.text;
|
|
3422
|
+
if (obj?.type !== "identifier" || !prop) return;
|
|
3423
|
+
if (prop === "use") {
|
|
3424
|
+
const m = parseUseMount(node);
|
|
3425
|
+
if (m) getVar(obj.text).mounts.push(m);
|
|
3426
|
+
} else if (ROUTER_METHODS.has(prop.toLowerCase())) {
|
|
3427
|
+
const first = namedArgs(node.childForFieldName("arguments"))[0];
|
|
3428
|
+
const p = first ? staticStringText(first) : null;
|
|
3429
|
+
if (p !== null && p.startsWith("/")) getVar(obj.text).declares = true;
|
|
3430
|
+
}
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
if (node.type === "export_statement") {
|
|
3434
|
+
let clause = null;
|
|
3435
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3436
|
+
const c = node.namedChild(i);
|
|
3437
|
+
if (c?.type === "export_clause") clause = c;
|
|
3438
|
+
}
|
|
3439
|
+
if (clause) {
|
|
3440
|
+
for (let i = 0; i < clause.namedChildCount; i++) {
|
|
3441
|
+
const spec = clause.namedChild(i);
|
|
3442
|
+
if (spec?.type !== "export_specifier") continue;
|
|
3443
|
+
const local = spec.childForFieldName("name")?.text;
|
|
3444
|
+
if (!local) continue;
|
|
3445
|
+
const exportedAs = spec.childForFieldName("alias")?.text ?? local;
|
|
3446
|
+
if (exportedAs === "default") exportDefaultName = local;
|
|
3447
|
+
else exportNamed.set(exportedAs, local);
|
|
3448
|
+
}
|
|
3449
|
+
return;
|
|
3450
|
+
}
|
|
3451
|
+
if (node.childForFieldName("declaration")) return;
|
|
3452
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3453
|
+
const c = node.namedChild(i);
|
|
3454
|
+
if (c && c.type !== "export_clause") {
|
|
3455
|
+
exportDefaultName = refFromExpr(c, "#default");
|
|
3456
|
+
break;
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
return;
|
|
3460
|
+
}
|
|
3461
|
+
if (node.type === "assignment_expression") {
|
|
3462
|
+
const left = node.childForFieldName("left");
|
|
3463
|
+
const right = node.childForFieldName("right");
|
|
3464
|
+
if (left?.type !== "member_expression" || !right) return;
|
|
3465
|
+
const lobj = left.childForFieldName("object")?.text;
|
|
3466
|
+
const lprop = left.childForFieldName("property")?.text;
|
|
3467
|
+
if (lobj === "module" && lprop === "exports") exportDefaultName = refFromExpr(right, "#default");
|
|
3468
|
+
else if (lobj === "exports" && lprop) {
|
|
3469
|
+
const key = refFromExpr(right, `#exp:${lprop}`);
|
|
3470
|
+
if (key) exportNamed.set(lprop, key);
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
});
|
|
3474
|
+
return {
|
|
3475
|
+
dir,
|
|
3476
|
+
routerVars,
|
|
3477
|
+
appVars,
|
|
3478
|
+
exportDefaultName,
|
|
3479
|
+
exportNamed,
|
|
3480
|
+
rawBindings: bindings,
|
|
3481
|
+
importedRouters: /* @__PURE__ */ new Map()
|
|
3482
|
+
};
|
|
3483
|
+
}
|
|
3484
|
+
async function expressMountPrefixes(files, serviceDir, tsPaths) {
|
|
3485
|
+
const jsParser = makeJsParser2();
|
|
3486
|
+
const fileInfo = /* @__PURE__ */ new Map();
|
|
3487
|
+
for (const f of files) {
|
|
3488
|
+
if (!JS_ROUTE_EXTENSIONS.has(import_node_path9.default.extname(f.path))) continue;
|
|
3489
|
+
if (isTestPath(f.path)) continue;
|
|
3490
|
+
const rel = toPosix(import_node_path9.default.relative(serviceDir, f.path));
|
|
3491
|
+
try {
|
|
3492
|
+
const tree = parseSource2(jsParser, f.content);
|
|
3493
|
+
fileInfo.set(rel, analyzeExpressFile(tree.rootNode, import_node_path9.default.dirname(f.path)));
|
|
3494
|
+
} catch {
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
if (fileInfo.size === 0) return /* @__PURE__ */ new Map();
|
|
3498
|
+
for (const info of fileInfo.values()) {
|
|
3499
|
+
for (const b of info.rawBindings) {
|
|
3500
|
+
const resolved = await resolveJsImport(b.specifier, info.dir, serviceDir, tsPaths);
|
|
3501
|
+
if (!resolved || !fileInfo.has(resolved)) continue;
|
|
3502
|
+
info.importedRouters.set(b.local, { file: resolved, sel: b.sel === "namespace" ? "default" : b.sel });
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
const resolveTarget = (name, file) => {
|
|
3506
|
+
const info = fileInfo.get(file);
|
|
3507
|
+
if (!info) return null;
|
|
3508
|
+
if (info.routerVars.has(name)) return { file, name };
|
|
3509
|
+
const imp = info.importedRouters.get(name);
|
|
3510
|
+
if (!imp) return null;
|
|
3511
|
+
const target = fileInfo.get(imp.file);
|
|
3512
|
+
if (!target) return null;
|
|
3513
|
+
const key = imp.sel === "default" ? target.exportDefaultName : target.exportNamed.get(imp.sel);
|
|
3514
|
+
if (!key) return null;
|
|
3515
|
+
return { file: imp.file, name: key };
|
|
3516
|
+
};
|
|
3517
|
+
const filePrefix = /* @__PURE__ */ new Map();
|
|
3518
|
+
const conflicted = /* @__PURE__ */ new Set();
|
|
3519
|
+
const apply = (file, prefix) => {
|
|
3520
|
+
if (conflicted.has(file)) return;
|
|
3521
|
+
const existing = filePrefix.get(file);
|
|
3522
|
+
if (existing === void 0) filePrefix.set(file, prefix);
|
|
3523
|
+
else if (existing !== prefix) {
|
|
3524
|
+
filePrefix.delete(file);
|
|
3525
|
+
conflicted.add(file);
|
|
3526
|
+
}
|
|
3527
|
+
};
|
|
3528
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3529
|
+
const collect = (file, name, accPrefix) => {
|
|
3530
|
+
const key = `${file}|${name}|${accPrefix}`;
|
|
3531
|
+
if (visited.has(key)) return;
|
|
3532
|
+
visited.add(key);
|
|
3533
|
+
const info = fileInfo.get(file);
|
|
3534
|
+
const rv = info?.routerVars.get(name);
|
|
3535
|
+
if (!info || !rv) return;
|
|
3536
|
+
if (rv.declares && info.appVars.size === 0) apply(file, accPrefix);
|
|
3537
|
+
for (const m of rv.mounts) {
|
|
3538
|
+
if (!m.target) continue;
|
|
3539
|
+
const t = resolveTarget(m.target, file);
|
|
3540
|
+
if (t) collect(t.file, t.name, accPrefix + m.prefix);
|
|
3541
|
+
}
|
|
3542
|
+
if (rv.aliasOf) {
|
|
3543
|
+
const t = resolveTarget(rv.aliasOf, file);
|
|
3544
|
+
if (t) collect(t.file, t.name, accPrefix);
|
|
3545
|
+
}
|
|
3546
|
+
};
|
|
3547
|
+
for (const [rel, info] of fileInfo) {
|
|
3548
|
+
for (const appVar of info.appVars) collect(rel, appVar, "");
|
|
3549
|
+
}
|
|
3550
|
+
const out = /* @__PURE__ */ new Map();
|
|
3551
|
+
for (const [file, prefix] of filePrefix) if (prefix && prefix !== "/") out.set(file, prefix);
|
|
3552
|
+
return out;
|
|
3553
|
+
}
|
|
2859
3554
|
async function addRoutes(graph, services) {
|
|
2860
|
-
const jsParser =
|
|
2861
|
-
const pyParser =
|
|
2862
|
-
const goParser =
|
|
3555
|
+
const jsParser = makeJsParser2();
|
|
3556
|
+
const pyParser = makePyParser2();
|
|
3557
|
+
const goParser = makeGoParser2();
|
|
2863
3558
|
let nodesAdded = 0;
|
|
2864
3559
|
let edgesAdded = 0;
|
|
2865
3560
|
for (const service of services) {
|
|
@@ -2879,13 +3574,14 @@ async function addRoutes(graph, services) {
|
|
|
2879
3574
|
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin)
|
|
2880
3575
|
continue;
|
|
2881
3576
|
const files = await loadSourceFiles(service.dir);
|
|
3577
|
+
const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
|
|
2882
3578
|
for (const file of files) {
|
|
2883
3579
|
if (isTestPath(file.path)) continue;
|
|
2884
|
-
const ext =
|
|
3580
|
+
const ext = import_node_path9.default.extname(file.path);
|
|
2885
3581
|
const isPy = ext === ".py";
|
|
2886
3582
|
const isGo = ext === ".go";
|
|
2887
3583
|
if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy && !isGo) continue;
|
|
2888
|
-
const relFile = toPosix(
|
|
3584
|
+
const relFile = toPosix(import_node_path9.default.relative(service.dir, file.path));
|
|
2889
3585
|
let routes;
|
|
2890
3586
|
try {
|
|
2891
3587
|
if (isGo) {
|
|
@@ -2907,16 +3603,18 @@ async function addRoutes(graph, services) {
|
|
|
2907
3603
|
continue;
|
|
2908
3604
|
}
|
|
2909
3605
|
if (routes.length === 0) continue;
|
|
3606
|
+
const mountPrefix = mountPrefixes.get(relFile);
|
|
2910
3607
|
for (const route of routes) {
|
|
2911
|
-
const
|
|
3608
|
+
const pathTemplate = mountPrefix ? canonicalizeTemplate(mountPrefix + route.pathTemplate) : route.pathTemplate;
|
|
3609
|
+
const rid = (0, import_types6.routeId)(service.pkg.name, route.method, pathTemplate);
|
|
2912
3610
|
if (!graph.hasNode(rid)) {
|
|
2913
3611
|
const node = {
|
|
2914
3612
|
id: rid,
|
|
2915
|
-
type:
|
|
2916
|
-
name: `${route.method} ${
|
|
3613
|
+
type: import_types6.NodeType.RouteNode,
|
|
3614
|
+
name: `${route.method} ${pathTemplate}`,
|
|
2917
3615
|
service: service.pkg.name,
|
|
2918
3616
|
method: route.method,
|
|
2919
|
-
pathTemplate
|
|
3617
|
+
pathTemplate,
|
|
2920
3618
|
path: relFile,
|
|
2921
3619
|
line: route.line,
|
|
2922
3620
|
framework: route.framework,
|
|
@@ -2925,15 +3623,15 @@ async function addRoutes(graph, services) {
|
|
|
2925
3623
|
graph.addNode(rid, node);
|
|
2926
3624
|
nodesAdded++;
|
|
2927
3625
|
}
|
|
2928
|
-
const containsId = (0,
|
|
3626
|
+
const containsId = (0, import_types6.extractedEdgeId)(service.node.id, rid, import_types6.EdgeType.CONTAINS);
|
|
2929
3627
|
if (!graph.hasEdge(containsId)) {
|
|
2930
3628
|
const edge = {
|
|
2931
3629
|
id: containsId,
|
|
2932
3630
|
source: service.node.id,
|
|
2933
3631
|
target: rid,
|
|
2934
|
-
type:
|
|
2935
|
-
provenance:
|
|
2936
|
-
confidence: (0,
|
|
3632
|
+
type: import_types6.EdgeType.CONTAINS,
|
|
3633
|
+
provenance: import_types6.Provenance.EXTRACTED,
|
|
3634
|
+
confidence: (0, import_types6.confidenceForExtracted)("structural"),
|
|
2937
3635
|
evidence: {
|
|
2938
3636
|
file: relFile,
|
|
2939
3637
|
line: route.line,
|
|
@@ -3169,7 +3867,7 @@ function languageForExt(relPath) {
|
|
|
3169
3867
|
function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
|
|
3170
3868
|
let p = toPosix2(filepath).replace(/^file:\/\//, "");
|
|
3171
3869
|
if (scanPath && scanPath.length > 0) {
|
|
3172
|
-
const absRoot = toPosix2(
|
|
3870
|
+
const absRoot = toPosix2(import_node_path10.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
|
|
3173
3871
|
const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
|
|
3174
3872
|
if (p.startsWith(anchor)) return p.slice(anchor.length);
|
|
3175
3873
|
}
|
|
@@ -3197,10 +3895,10 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
3197
3895
|
entry = null;
|
|
3198
3896
|
const mapPath = `${absFilepath}.map`;
|
|
3199
3897
|
try {
|
|
3200
|
-
if ((0,
|
|
3201
|
-
const raw = JSON.parse((0,
|
|
3898
|
+
if ((0, import_node_fs9.existsSync)(mapPath)) {
|
|
3899
|
+
const raw = JSON.parse((0, import_node_fs9.readFileSync)(mapPath, "utf8"));
|
|
3202
3900
|
const consumer = new sourceMapJs.SourceMapConsumer(raw);
|
|
3203
|
-
entry = { consumer, dir:
|
|
3901
|
+
entry = { consumer, dir: import_node_path10.default.dirname(mapPath) };
|
|
3204
3902
|
}
|
|
3205
3903
|
} catch {
|
|
3206
3904
|
entry = null;
|
|
@@ -3215,7 +3913,7 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
3215
3913
|
});
|
|
3216
3914
|
if (!pos || !pos.source) return null;
|
|
3217
3915
|
const root = entry.consumer.sourceRoot ?? "";
|
|
3218
|
-
const resolved =
|
|
3916
|
+
const resolved = import_node_path10.default.resolve(entry.dir, root, pos.source);
|
|
3219
3917
|
return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
|
|
3220
3918
|
} catch {
|
|
3221
3919
|
return null;
|
|
@@ -3248,11 +3946,11 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
|
3248
3946
|
};
|
|
3249
3947
|
}
|
|
3250
3948
|
function reconcileObservedRelPath(graph, serviceName, relPath) {
|
|
3251
|
-
if (graph.hasNode((0,
|
|
3949
|
+
if (graph.hasNode((0, import_types7.fileId)(serviceName, relPath))) return relPath;
|
|
3252
3950
|
let best = null;
|
|
3253
3951
|
graph.forEachNode((_id, attrs) => {
|
|
3254
3952
|
const a = attrs;
|
|
3255
|
-
if (a.type !==
|
|
3953
|
+
if (a.type !== import_types7.NodeType.FileNode || a.service !== serviceName) return;
|
|
3256
3954
|
if (a.discoveredVia === "otel") return;
|
|
3257
3955
|
const p = a.path;
|
|
3258
3956
|
if (!p) return;
|
|
@@ -3264,14 +3962,14 @@ function reconcileObservedRelPath(graph, serviceName, relPath) {
|
|
|
3264
3962
|
}
|
|
3265
3963
|
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
3266
3964
|
const svcAttrs = graph.hasNode(serviceNodeId) ? graph.getNodeAttributes(serviceNodeId) : void 0;
|
|
3267
|
-
const canonicalService = svcAttrs && svcAttrs.type ===
|
|
3965
|
+
const canonicalService = svcAttrs && svcAttrs.type === import_types7.NodeType.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
|
|
3268
3966
|
const relPath = reconcileObservedRelPath(graph, canonicalService, callSite.relPath);
|
|
3269
|
-
const fileNodeId = (0,
|
|
3967
|
+
const fileNodeId = (0, import_types7.fileId)(canonicalService, relPath);
|
|
3270
3968
|
if (!graph.hasNode(fileNodeId)) {
|
|
3271
3969
|
const language = languageForExt(relPath);
|
|
3272
3970
|
const node = {
|
|
3273
3971
|
id: fileNodeId,
|
|
3274
|
-
type:
|
|
3972
|
+
type: import_types7.NodeType.FileNode,
|
|
3275
3973
|
service: canonicalService,
|
|
3276
3974
|
path: relPath,
|
|
3277
3975
|
...language ? { language } : {},
|
|
@@ -3280,14 +3978,14 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
|
3280
3978
|
};
|
|
3281
3979
|
graph.addNode(fileNodeId, node);
|
|
3282
3980
|
}
|
|
3283
|
-
const containsId = makeObservedEdgeId(
|
|
3981
|
+
const containsId = makeObservedEdgeId(import_types7.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
|
|
3284
3982
|
if (!graph.hasEdge(containsId)) {
|
|
3285
3983
|
const edge = {
|
|
3286
3984
|
id: containsId,
|
|
3287
3985
|
source: serviceNodeId,
|
|
3288
3986
|
target: fileNodeId,
|
|
3289
|
-
type:
|
|
3290
|
-
provenance:
|
|
3987
|
+
type: import_types7.EdgeType.CONTAINS,
|
|
3988
|
+
provenance: import_types7.Provenance.OBSERVED
|
|
3291
3989
|
};
|
|
3292
3990
|
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3293
3991
|
}
|
|
@@ -3311,11 +4009,11 @@ function pickContainingSymbol(candidates, fn) {
|
|
|
3311
4009
|
return [...candidates].sort(bySpan)[0].id;
|
|
3312
4010
|
}
|
|
3313
4011
|
function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line) {
|
|
3314
|
-
const sid = (0,
|
|
4012
|
+
const sid = (0, import_types7.symbolId)(service, relPath, fn);
|
|
3315
4013
|
if (!graph.hasNode(sid)) {
|
|
3316
4014
|
const node = {
|
|
3317
4015
|
id: sid,
|
|
3318
|
-
type:
|
|
4016
|
+
type: import_types7.NodeType.SymbolNode,
|
|
3319
4017
|
kind: "function",
|
|
3320
4018
|
qualname: fn,
|
|
3321
4019
|
span: { startLine: line, endLine: line },
|
|
@@ -3325,14 +4023,14 @@ function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line)
|
|
|
3325
4023
|
};
|
|
3326
4024
|
graph.addNode(sid, node);
|
|
3327
4025
|
}
|
|
3328
|
-
const containsId = makeObservedEdgeId(
|
|
4026
|
+
const containsId = makeObservedEdgeId(import_types7.EdgeType.CONTAINS, fileNodeId, sid);
|
|
3329
4027
|
if (!graph.hasEdge(containsId)) {
|
|
3330
4028
|
const edge = {
|
|
3331
4029
|
id: containsId,
|
|
3332
4030
|
source: fileNodeId,
|
|
3333
4031
|
target: sid,
|
|
3334
|
-
type:
|
|
3335
|
-
provenance:
|
|
4032
|
+
type: import_types7.EdgeType.CONTAINS,
|
|
4033
|
+
provenance: import_types7.Provenance.OBSERVED
|
|
3336
4034
|
};
|
|
3337
4035
|
graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
|
|
3338
4036
|
}
|
|
@@ -3344,9 +4042,9 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
|
|
|
3344
4042
|
let sawSymbol = false;
|
|
3345
4043
|
const candidates = [];
|
|
3346
4044
|
graph.forEachOutboundEdge(fileNodeId, (_edge, edgeAttrs, _source, target) => {
|
|
3347
|
-
if (edgeAttrs.type !==
|
|
4045
|
+
if (edgeAttrs.type !== import_types7.EdgeType.CONTAINS) return;
|
|
3348
4046
|
const t = graph.getNodeAttributes(target);
|
|
3349
|
-
if (t.type !==
|
|
4047
|
+
if (t.type !== import_types7.NodeType.SymbolNode) return;
|
|
3350
4048
|
sawSymbol = true;
|
|
3351
4049
|
if (line >= t.span.startLine && line <= t.span.endLine) {
|
|
3352
4050
|
candidates.push({ id: target, symbol: t });
|
|
@@ -3359,17 +4057,17 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
|
|
|
3359
4057
|
return fileNodeId;
|
|
3360
4058
|
}
|
|
3361
4059
|
function makeObservedEdgeId(type, source, target) {
|
|
3362
|
-
return (0,
|
|
4060
|
+
return (0, import_types7.observedEdgeId)(source, target, type);
|
|
3363
4061
|
}
|
|
3364
4062
|
function makeInferredEdgeId(type, source, target) {
|
|
3365
|
-
return (0,
|
|
4063
|
+
return (0, import_types7.inferredEdgeId)(source, target, type);
|
|
3366
4064
|
}
|
|
3367
4065
|
var INFERRED_CONFIDENCE = 0.6;
|
|
3368
4066
|
var STITCH_MAX_DEPTH = 2;
|
|
3369
4067
|
var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
4068
|
+
import_types7.EdgeType.CALLS,
|
|
4069
|
+
import_types7.EdgeType.CONNECTS_TO,
|
|
4070
|
+
import_types7.EdgeType.DEPENDS_ON
|
|
3373
4071
|
]);
|
|
3374
4072
|
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
3375
4073
|
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
@@ -3385,11 +4083,11 @@ function spanServesGraphqlOperation(kind) {
|
|
|
3385
4083
|
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
3386
4084
|
}
|
|
3387
4085
|
function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
|
|
3388
|
-
const id = (0,
|
|
4086
|
+
const id = (0, import_types7.graphqlOperationId)(serviceName, operationType, operationName);
|
|
3389
4087
|
if (graph.hasNode(id)) return id;
|
|
3390
4088
|
const node = {
|
|
3391
4089
|
id,
|
|
3392
|
-
type:
|
|
4090
|
+
type: import_types7.NodeType.GraphQLOperationNode,
|
|
3393
4091
|
name: operationName,
|
|
3394
4092
|
service: serviceName,
|
|
3395
4093
|
operationType: operationType.toLowerCase(),
|
|
@@ -3403,11 +4101,11 @@ function spanServesGrpcMethod(kind) {
|
|
|
3403
4101
|
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
3404
4102
|
}
|
|
3405
4103
|
function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
|
|
3406
|
-
const id = (0,
|
|
4104
|
+
const id = (0, import_types7.grpcMethodId)(rpcService, rpcMethod);
|
|
3407
4105
|
if (graph.hasNode(id)) return id;
|
|
3408
4106
|
const node = {
|
|
3409
4107
|
id,
|
|
3410
|
-
type:
|
|
4108
|
+
type: import_types7.NodeType.GrpcMethodNode,
|
|
3411
4109
|
name: `${rpcService}/${rpcMethod}`,
|
|
3412
4110
|
rpcService,
|
|
3413
4111
|
rpcMethod,
|
|
@@ -3420,11 +4118,11 @@ function spanServesWebsocketChannel(kind) {
|
|
|
3420
4118
|
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
3421
4119
|
}
|
|
3422
4120
|
function ensureWebsocketChannelNode(graph, serviceName, channel) {
|
|
3423
|
-
const id = (0,
|
|
4121
|
+
const id = (0, import_types7.websocketChannelId)(serviceName, channel);
|
|
3424
4122
|
if (graph.hasNode(id)) return id;
|
|
3425
4123
|
const node = {
|
|
3426
4124
|
id,
|
|
3427
|
-
type:
|
|
4125
|
+
type: import_types7.NodeType.WebSocketChannelNode,
|
|
3428
4126
|
name: channel,
|
|
3429
4127
|
service: serviceName,
|
|
3430
4128
|
channel,
|
|
@@ -3437,11 +4135,11 @@ function messagingDestinationKind(system) {
|
|
|
3437
4135
|
return `${system}-topic`;
|
|
3438
4136
|
}
|
|
3439
4137
|
function ensureMessagingDestinationNode(graph, system, destination) {
|
|
3440
|
-
const id = (0,
|
|
4138
|
+
const id = (0, import_types7.infraId)(messagingDestinationKind(system), destination);
|
|
3441
4139
|
if (graph.hasNode(id)) return id;
|
|
3442
4140
|
const node = {
|
|
3443
4141
|
id,
|
|
3444
|
-
type:
|
|
4142
|
+
type: import_types7.NodeType.InfraNode,
|
|
3445
4143
|
name: destination,
|
|
3446
4144
|
provider: "self",
|
|
3447
4145
|
kind: messagingDestinationKind(system)
|
|
@@ -3485,9 +4183,9 @@ function lookupParentSpan(traceId, parentSpanId, now) {
|
|
|
3485
4183
|
};
|
|
3486
4184
|
}
|
|
3487
4185
|
function resolveServiceId(graph, host, env) {
|
|
3488
|
-
const envTagged = (0,
|
|
4186
|
+
const envTagged = (0, import_types7.serviceId)(host, env);
|
|
3489
4187
|
if (graph.hasNode(envTagged)) return envTagged;
|
|
3490
|
-
const envLess = (0,
|
|
4188
|
+
const envLess = (0, import_types7.serviceId)(host);
|
|
3491
4189
|
if (envLess !== envTagged && graph.hasNode(envLess)) return envLess;
|
|
3492
4190
|
let sameEnv = null;
|
|
3493
4191
|
let envLessMatch = null;
|
|
@@ -3495,7 +4193,7 @@ function resolveServiceId(graph, host, env) {
|
|
|
3495
4193
|
graph.forEachNode((id, attrs) => {
|
|
3496
4194
|
if (sameEnv) return;
|
|
3497
4195
|
const a = attrs;
|
|
3498
|
-
if (a.type !==
|
|
4196
|
+
if (a.type !== import_types7.NodeType.ServiceNode) return;
|
|
3499
4197
|
const matchesByName = a.name === host;
|
|
3500
4198
|
const matchesByAlias = a.aliases ? a.aliases.includes(host) : false;
|
|
3501
4199
|
if (!matchesByName && !matchesByAlias) return;
|
|
@@ -3510,14 +4208,14 @@ function resolveServiceId(graph, host, env) {
|
|
|
3510
4208
|
return sameEnv ?? envLessMatch ?? anyMatch;
|
|
3511
4209
|
}
|
|
3512
4210
|
function frontierIdFor(host) {
|
|
3513
|
-
return (0,
|
|
4211
|
+
return (0, import_types7.frontierId)(host);
|
|
3514
4212
|
}
|
|
3515
4213
|
function ensureServiceNode(graph, serviceName, env) {
|
|
3516
|
-
const id = (0,
|
|
4214
|
+
const id = (0, import_types7.serviceId)(serviceName, env);
|
|
3517
4215
|
if (graph.hasNode(id)) return id;
|
|
3518
4216
|
const wanted = serviceName.toLowerCase();
|
|
3519
4217
|
const extractedId = graph.findNode((_nid, attrs) => {
|
|
3520
|
-
if (attrs.type !==
|
|
4218
|
+
if (attrs.type !== import_types7.NodeType.ServiceNode) return false;
|
|
3521
4219
|
const svc = attrs;
|
|
3522
4220
|
if (svc.discoveredVia === "otel") return false;
|
|
3523
4221
|
return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
|
|
@@ -3525,7 +4223,7 @@ function ensureServiceNode(graph, serviceName, env) {
|
|
|
3525
4223
|
if (extractedId) return extractedId;
|
|
3526
4224
|
const node = {
|
|
3527
4225
|
id,
|
|
3528
|
-
type:
|
|
4226
|
+
type: import_types7.NodeType.ServiceNode,
|
|
3529
4227
|
name: serviceName,
|
|
3530
4228
|
language: "unknown",
|
|
3531
4229
|
discoveredVia: "otel",
|
|
@@ -3535,11 +4233,11 @@ function ensureServiceNode(graph, serviceName, env) {
|
|
|
3535
4233
|
return id;
|
|
3536
4234
|
}
|
|
3537
4235
|
function ensureInfraNode(graph, kind, name, provider) {
|
|
3538
|
-
const id = (0,
|
|
4236
|
+
const id = (0, import_types7.infraId)(kind, name);
|
|
3539
4237
|
if (graph.hasNode(id)) return id;
|
|
3540
4238
|
const node = {
|
|
3541
4239
|
id,
|
|
3542
|
-
type:
|
|
4240
|
+
type: import_types7.NodeType.InfraNode,
|
|
3543
4241
|
name,
|
|
3544
4242
|
provider,
|
|
3545
4243
|
kind
|
|
@@ -3551,7 +4249,7 @@ var COLUMN_BEARING_INFRA_KINDS = /* @__PURE__ */ new Set(["sql-table", "supabase
|
|
|
3551
4249
|
function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
|
|
3552
4250
|
if (!columns || columns.length === 0 || !graph.hasNode(tableNodeId)) return;
|
|
3553
4251
|
const node = graph.getNodeAttributes(tableNodeId);
|
|
3554
|
-
if (node.type !==
|
|
4252
|
+
if (node.type !== import_types7.NodeType.InfraNode || !node.kind || !COLUMN_BEARING_INFRA_KINDS.has(node.kind)) {
|
|
3555
4253
|
return;
|
|
3556
4254
|
}
|
|
3557
4255
|
graph.replaceNodeAttributes(tableNodeId, {
|
|
@@ -3560,14 +4258,14 @@ function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
|
|
|
3560
4258
|
});
|
|
3561
4259
|
}
|
|
3562
4260
|
function mergeObservedColumns(graph, tableNodeId, columns) {
|
|
3563
|
-
mergeColumnsAt(graph, tableNodeId, columns,
|
|
4261
|
+
mergeColumnsAt(graph, tableNodeId, columns, import_types7.Provenance.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
|
|
3564
4262
|
}
|
|
3565
4263
|
function ensureDatabaseNode(graph, host, engine) {
|
|
3566
|
-
const id = (0,
|
|
4264
|
+
const id = (0, import_types7.databaseId)(host);
|
|
3567
4265
|
if (graph.hasNode(id)) return id;
|
|
3568
4266
|
const node = {
|
|
3569
4267
|
id,
|
|
3570
|
-
type:
|
|
4268
|
+
type: import_types7.NodeType.DatabaseNode,
|
|
3571
4269
|
name: host,
|
|
3572
4270
|
engine,
|
|
3573
4271
|
engineVersion: "unknown",
|
|
@@ -3579,11 +4277,11 @@ function ensureDatabaseNode(graph, host, engine) {
|
|
|
3579
4277
|
return id;
|
|
3580
4278
|
}
|
|
3581
4279
|
function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
|
|
3582
|
-
const id = (0,
|
|
4280
|
+
const id = (0, import_types7.localDatabaseId)(serviceName, name);
|
|
3583
4281
|
if (graph.hasNode(id)) return id;
|
|
3584
4282
|
const node = {
|
|
3585
4283
|
id,
|
|
3586
|
-
type:
|
|
4284
|
+
type: import_types7.NodeType.DatabaseNode,
|
|
3587
4285
|
name,
|
|
3588
4286
|
engine,
|
|
3589
4287
|
engineVersion: "unknown",
|
|
@@ -3598,17 +4296,17 @@ function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
|
|
|
3598
4296
|
const sources = [serviceNodeId];
|
|
3599
4297
|
for (const edgeId of graph.outboundEdges(serviceNodeId)) {
|
|
3600
4298
|
const e = graph.getEdgeAttributes(edgeId);
|
|
3601
|
-
if (e.type ===
|
|
4299
|
+
if (e.type === import_types7.EdgeType.CONTAINS) sources.push(e.target);
|
|
3602
4300
|
}
|
|
3603
4301
|
const matches = /* @__PURE__ */ new Set();
|
|
3604
4302
|
for (const src of sources) {
|
|
3605
4303
|
if (!graph.hasNode(src)) continue;
|
|
3606
4304
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
3607
4305
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3608
|
-
if (edge.type !==
|
|
4306
|
+
if (edge.type !== import_types7.EdgeType.CONNECTS_TO || edge.provenance !== import_types7.Provenance.EXTRACTED) continue;
|
|
3609
4307
|
if (!graph.hasNode(edge.target)) continue;
|
|
3610
4308
|
const target = graph.getNodeAttributes(edge.target);
|
|
3611
|
-
if (target.type !==
|
|
4309
|
+
if (target.type !== import_types7.NodeType.DatabaseNode || target.engine !== engine) continue;
|
|
3612
4310
|
matches.add(edge.target);
|
|
3613
4311
|
}
|
|
3614
4312
|
}
|
|
@@ -3623,7 +4321,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
3623
4321
|
}
|
|
3624
4322
|
const node = {
|
|
3625
4323
|
id,
|
|
3626
|
-
type:
|
|
4324
|
+
type: import_types7.NodeType.FrontierNode,
|
|
3627
4325
|
name: host,
|
|
3628
4326
|
host,
|
|
3629
4327
|
firstObserved: ts,
|
|
@@ -3647,11 +4345,11 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
|
|
|
3647
4345
|
};
|
|
3648
4346
|
const updated = {
|
|
3649
4347
|
...existing,
|
|
3650
|
-
provenance:
|
|
4348
|
+
provenance: import_types7.Provenance.OBSERVED,
|
|
3651
4349
|
lastObserved: ts,
|
|
3652
4350
|
callCount: newSpanCount,
|
|
3653
4351
|
signal: newSignal,
|
|
3654
|
-
confidence: (0,
|
|
4352
|
+
confidence: (0, import_types7.confidenceForObservedSignal)(newSignal),
|
|
3655
4353
|
grain
|
|
3656
4354
|
// backfills legacy edges that predate ADR-142
|
|
3657
4355
|
};
|
|
@@ -3668,8 +4366,8 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
|
|
|
3668
4366
|
source,
|
|
3669
4367
|
target,
|
|
3670
4368
|
type,
|
|
3671
|
-
provenance:
|
|
3672
|
-
confidence: (0,
|
|
4369
|
+
provenance: import_types7.Provenance.OBSERVED,
|
|
4370
|
+
confidence: (0, import_types7.confidenceForObservedSignal)(signal),
|
|
3673
4371
|
lastObserved: ts,
|
|
3674
4372
|
callCount: 1,
|
|
3675
4373
|
signal,
|
|
@@ -3691,9 +4389,9 @@ function stitchTrace(graph, sourceServiceId, ts) {
|
|
|
3691
4389
|
const outbound = graph.outboundEdges(nodeId);
|
|
3692
4390
|
for (const edgeId of outbound) {
|
|
3693
4391
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3694
|
-
if (edge.provenance !==
|
|
4392
|
+
if (edge.provenance !== import_types7.Provenance.EXTRACTED) continue;
|
|
3695
4393
|
if (!STITCH_EDGE_TYPES.has(edge.type)) continue;
|
|
3696
|
-
if (graph.hasEdge((0,
|
|
4394
|
+
if (graph.hasEdge((0, import_types7.observedEdgeId)(edge.source, edge.target, edge.type))) continue;
|
|
3697
4395
|
upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts);
|
|
3698
4396
|
if (!visited.has(edge.target)) {
|
|
3699
4397
|
visited.add(edge.target);
|
|
@@ -3715,23 +4413,23 @@ function upsertInferredEdge(graph, type, source, target, ts) {
|
|
|
3715
4413
|
source,
|
|
3716
4414
|
target,
|
|
3717
4415
|
type,
|
|
3718
|
-
provenance:
|
|
4416
|
+
provenance: import_types7.Provenance.INFERRED,
|
|
3719
4417
|
confidence: INFERRED_CONFIDENCE,
|
|
3720
4418
|
lastObserved: ts
|
|
3721
4419
|
};
|
|
3722
4420
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
3723
4421
|
}
|
|
3724
4422
|
async function appendErrorEvent(ctx, ev) {
|
|
3725
|
-
await
|
|
3726
|
-
await
|
|
4423
|
+
await import_node_fs9.promises.mkdir(import_node_path10.default.dirname(ctx.errorsPath), { recursive: true });
|
|
4424
|
+
await import_node_fs9.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
3727
4425
|
}
|
|
3728
4426
|
function incidentAffectedNode(span, graph, scanPath) {
|
|
3729
|
-
const sid = (0,
|
|
4427
|
+
const sid = (0, import_types7.serviceId)(span.service, span.env);
|
|
3730
4428
|
const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
|
|
3731
4429
|
const callSite = callSiteFromSpan(span, serviceNode, scanPath);
|
|
3732
4430
|
if (callSite) {
|
|
3733
4431
|
const relPath = graph ? reconcileObservedRelPath(graph, span.service, callSite.relPath) : callSite.relPath;
|
|
3734
|
-
return (0,
|
|
4432
|
+
return (0, import_types7.fileId)(span.service, relPath);
|
|
3735
4433
|
}
|
|
3736
4434
|
return sid;
|
|
3737
4435
|
}
|
|
@@ -3831,7 +4529,7 @@ function findRouteNodeByHttpRoute(graph, serviceName, method, httpRoute) {
|
|
|
3831
4529
|
graph.forEachNode((id, attrs) => {
|
|
3832
4530
|
if (found) return;
|
|
3833
4531
|
const a = attrs;
|
|
3834
|
-
if (a.type !==
|
|
4532
|
+
if (a.type !== import_types7.NodeType.RouteNode || a.service !== serviceName) return;
|
|
3835
4533
|
if (m && a.method !== "ALL" && a.method !== m) return;
|
|
3836
4534
|
if (normalizePathTemplate(a.pathTemplate) === target) found = id;
|
|
3837
4535
|
});
|
|
@@ -3862,7 +4560,7 @@ async function handleSpan(ctx, span) {
|
|
|
3862
4560
|
let targetId;
|
|
3863
4561
|
if (host) {
|
|
3864
4562
|
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
3865
|
-
targetId = (0,
|
|
4563
|
+
targetId = (0, import_types7.databaseId)(host);
|
|
3866
4564
|
} else {
|
|
3867
4565
|
const declared = findDeclaredDatabaseForService(ctx.graph, sourceId, span.dbSystem);
|
|
3868
4566
|
if (declared) {
|
|
@@ -3879,7 +4577,7 @@ async function handleSpan(ctx, span) {
|
|
|
3879
4577
|
}
|
|
3880
4578
|
const result = upsertObservedEdge(
|
|
3881
4579
|
ctx.graph,
|
|
3882
|
-
|
|
4580
|
+
import_types7.EdgeType.CONNECTS_TO,
|
|
3883
4581
|
observedSource(),
|
|
3884
4582
|
targetId,
|
|
3885
4583
|
ts,
|
|
@@ -3891,7 +4589,7 @@ async function handleSpan(ctx, span) {
|
|
|
3891
4589
|
const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
|
|
3892
4590
|
upsertObservedEdge(
|
|
3893
4591
|
ctx.graph,
|
|
3894
|
-
|
|
4592
|
+
import_types7.EdgeType.CALLS,
|
|
3895
4593
|
observedSource(),
|
|
3896
4594
|
collectionId,
|
|
3897
4595
|
ts,
|
|
@@ -3903,7 +4601,7 @@ async function handleSpan(ctx, span) {
|
|
|
3903
4601
|
const tableId = ensureInfraNode(ctx.graph, "sql-table", span.dbTable, "self");
|
|
3904
4602
|
upsertObservedEdge(
|
|
3905
4603
|
ctx.graph,
|
|
3906
|
-
|
|
4604
|
+
import_types7.EdgeType.CALLS,
|
|
3907
4605
|
observedSource(),
|
|
3908
4606
|
tableId,
|
|
3909
4607
|
ts,
|
|
@@ -3919,7 +4617,7 @@ async function handleSpan(ctx, span) {
|
|
|
3919
4617
|
span.messagingSystem,
|
|
3920
4618
|
span.messagingDestination
|
|
3921
4619
|
);
|
|
3922
|
-
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ?
|
|
4620
|
+
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types7.EdgeType.CONSUMES_FROM : import_types7.EdgeType.PUBLISHES_TO;
|
|
3923
4621
|
const result = upsertObservedEdge(
|
|
3924
4622
|
ctx.graph,
|
|
3925
4623
|
edgeType,
|
|
@@ -3939,7 +4637,7 @@ async function handleSpan(ctx, span) {
|
|
|
3939
4637
|
);
|
|
3940
4638
|
const result = upsertObservedEdge(
|
|
3941
4639
|
ctx.graph,
|
|
3942
|
-
|
|
4640
|
+
import_types7.EdgeType.CONTAINS,
|
|
3943
4641
|
observedSource(),
|
|
3944
4642
|
targetId,
|
|
3945
4643
|
ts,
|
|
@@ -3951,7 +4649,7 @@ async function handleSpan(ctx, span) {
|
|
|
3951
4649
|
const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
|
|
3952
4650
|
const result = upsertObservedEdge(
|
|
3953
4651
|
ctx.graph,
|
|
3954
|
-
|
|
4652
|
+
import_types7.EdgeType.CONTAINS,
|
|
3955
4653
|
observedSource(),
|
|
3956
4654
|
targetId,
|
|
3957
4655
|
ts,
|
|
@@ -3967,7 +4665,7 @@ async function handleSpan(ctx, span) {
|
|
|
3967
4665
|
);
|
|
3968
4666
|
const result = upsertObservedEdge(
|
|
3969
4667
|
ctx.graph,
|
|
3970
|
-
|
|
4668
|
+
import_types7.EdgeType.CONNECTS_TO,
|
|
3971
4669
|
observedSource(),
|
|
3972
4670
|
targetId,
|
|
3973
4671
|
ts,
|
|
@@ -3983,7 +4681,7 @@ async function handleSpan(ctx, span) {
|
|
|
3983
4681
|
if (targetId && targetId !== sourceId) {
|
|
3984
4682
|
upsertObservedEdge(
|
|
3985
4683
|
ctx.graph,
|
|
3986
|
-
|
|
4684
|
+
import_types7.EdgeType.CALLS,
|
|
3987
4685
|
observedSource(),
|
|
3988
4686
|
targetId,
|
|
3989
4687
|
ts,
|
|
@@ -3996,7 +4694,7 @@ async function handleSpan(ctx, span) {
|
|
|
3996
4694
|
const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
|
|
3997
4695
|
upsertObservedEdge(
|
|
3998
4696
|
ctx.graph,
|
|
3999
|
-
|
|
4697
|
+
import_types7.EdgeType.CALLS,
|
|
4000
4698
|
observedSource(),
|
|
4001
4699
|
frontierNodeId,
|
|
4002
4700
|
ts,
|
|
@@ -4022,7 +4720,7 @@ async function handleSpan(ctx, span) {
|
|
|
4022
4720
|
} : void 0;
|
|
4023
4721
|
upsertObservedEdge(
|
|
4024
4722
|
ctx.graph,
|
|
4025
|
-
|
|
4723
|
+
import_types7.EdgeType.CALLS,
|
|
4026
4724
|
fallbackSource,
|
|
4027
4725
|
sourceId,
|
|
4028
4726
|
ts,
|
|
@@ -4041,7 +4739,7 @@ async function handleSpan(ctx, span) {
|
|
|
4041
4739
|
);
|
|
4042
4740
|
if (routeNodeId) {
|
|
4043
4741
|
const routeSvc = ctx.graph.getNodeAttributes(routeNodeId).service;
|
|
4044
|
-
upsertObservedEdge(ctx.graph,
|
|
4742
|
+
upsertObservedEdge(ctx.graph, import_types7.EdgeType.CONTAINS, (0, import_types7.serviceId)(routeSvc), routeNodeId, ts, isError);
|
|
4045
4743
|
}
|
|
4046
4744
|
}
|
|
4047
4745
|
if (span.statusCode === 2) {
|
|
@@ -4080,7 +4778,7 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
4080
4778
|
const aliasIndex = /* @__PURE__ */ new Map();
|
|
4081
4779
|
graph.forEachNode((id, attrs) => {
|
|
4082
4780
|
const a = attrs;
|
|
4083
|
-
if (a.type !==
|
|
4781
|
+
if (a.type !== import_types7.NodeType.ServiceNode) return;
|
|
4084
4782
|
aliasIndex.set(a.name, id);
|
|
4085
4783
|
if (a.aliases) {
|
|
4086
4784
|
for (const alias of a.aliases) aliasIndex.set(alias, id);
|
|
@@ -4089,7 +4787,7 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
4089
4787
|
const toPromote = [];
|
|
4090
4788
|
graph.forEachNode((id, attrs) => {
|
|
4091
4789
|
const a = attrs;
|
|
4092
|
-
if (a.type !==
|
|
4790
|
+
if (a.type !== import_types7.NodeType.FrontierNode) return;
|
|
4093
4791
|
const target = aliasIndex.get(a.host);
|
|
4094
4792
|
if (!target) return;
|
|
4095
4793
|
if (target === id) return;
|
|
@@ -4123,7 +4821,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId7) {
|
|
|
4123
4821
|
}
|
|
4124
4822
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
4125
4823
|
graph.dropEdge(oldEdgeId);
|
|
4126
|
-
const newId = edge.provenance ===
|
|
4824
|
+
const newId = edge.provenance === import_types7.Provenance.OBSERVED ? (0, import_types7.observedEdgeId)(newSource, newTarget, edge.type) : edge.provenance === import_types7.Provenance.INFERRED ? (0, import_types7.inferredEdgeId)(newSource, newTarget, edge.type) : (0, import_types7.extractedEdgeId)(newSource, newTarget, edge.type);
|
|
4127
4825
|
if (graph.hasEdge(newId)) {
|
|
4128
4826
|
const existing = graph.getEdgeAttributes(newId);
|
|
4129
4827
|
const merged = {
|
|
@@ -4157,12 +4855,12 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
4157
4855
|
const project = options.project ?? DEFAULT_PROJECT;
|
|
4158
4856
|
graph.forEachEdge((id, attrs) => {
|
|
4159
4857
|
const e = attrs;
|
|
4160
|
-
if (e.provenance !==
|
|
4858
|
+
if (e.provenance !== import_types7.Provenance.OBSERVED) return;
|
|
4161
4859
|
if (!e.lastObserved) return;
|
|
4162
4860
|
const threshold = thresholdForEdgeType(e.type, thresholds);
|
|
4163
4861
|
const age = now - new Date(e.lastObserved).getTime();
|
|
4164
4862
|
if (age > threshold) {
|
|
4165
|
-
const updated = { ...e, provenance:
|
|
4863
|
+
const updated = { ...e, provenance: import_types7.Provenance.STALE, confidence: 0.3 };
|
|
4166
4864
|
graph.replaceEdgeAttributes(id, updated);
|
|
4167
4865
|
events.push({
|
|
4168
4866
|
edgeId: id,
|
|
@@ -4179,8 +4877,8 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
4179
4877
|
project,
|
|
4180
4878
|
payload: {
|
|
4181
4879
|
edgeId: id,
|
|
4182
|
-
from:
|
|
4183
|
-
to:
|
|
4880
|
+
from: import_types7.Provenance.OBSERVED,
|
|
4881
|
+
to: import_types7.Provenance.STALE
|
|
4184
4882
|
}
|
|
4185
4883
|
});
|
|
4186
4884
|
}
|
|
@@ -4191,13 +4889,13 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
4191
4889
|
return { count: events.length, events };
|
|
4192
4890
|
}
|
|
4193
4891
|
async function appendStaleEvents(staleEventsPath, events) {
|
|
4194
|
-
await
|
|
4892
|
+
await import_node_fs9.promises.mkdir(import_node_path10.default.dirname(staleEventsPath), { recursive: true });
|
|
4195
4893
|
const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
4196
|
-
await
|
|
4894
|
+
await import_node_fs9.promises.appendFile(staleEventsPath, lines, "utf8");
|
|
4197
4895
|
}
|
|
4198
4896
|
async function readStaleEvents(staleEventsPath) {
|
|
4199
4897
|
try {
|
|
4200
|
-
const raw = await
|
|
4898
|
+
const raw = await import_node_fs9.promises.readFile(staleEventsPath, "utf8");
|
|
4201
4899
|
return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
4202
4900
|
} catch (err) {
|
|
4203
4901
|
if (err.code === "ENOENT") return [];
|
|
@@ -4231,7 +4929,7 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
4231
4929
|
}
|
|
4232
4930
|
async function readErrorEvents(errorsPath) {
|
|
4233
4931
|
try {
|
|
4234
|
-
const raw = await
|
|
4932
|
+
const raw = await import_node_fs9.promises.readFile(errorsPath, "utf8");
|
|
4235
4933
|
const events = raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
4236
4934
|
return dedupeIncidents(events);
|
|
4237
4935
|
} catch (err) {
|
|
@@ -4289,7 +4987,7 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
4289
4987
|
const validEdges = [];
|
|
4290
4988
|
for (const node of incomingNodes) {
|
|
4291
4989
|
if (node.attributes === void 0) continue;
|
|
4292
|
-
const parsed =
|
|
4990
|
+
const parsed = import_types7.GraphNodeSchema.safeParse(node.attributes);
|
|
4293
4991
|
if (!parsed.success) {
|
|
4294
4992
|
issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
|
|
4295
4993
|
continue;
|
|
@@ -4298,7 +4996,7 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
4298
4996
|
}
|
|
4299
4997
|
for (const edge of incomingEdges) {
|
|
4300
4998
|
if (edge.attributes === void 0) continue;
|
|
4301
|
-
const parsed =
|
|
4999
|
+
const parsed = import_types7.GraphEdgeSchema.safeParse(edge.attributes);
|
|
4302
5000
|
if (!parsed.success) {
|
|
4303
5001
|
const label = edge.key ?? `${edge.source}->${edge.target}`;
|
|
4304
5002
|
issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
|
|
@@ -4327,29 +5025,29 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
4327
5025
|
}
|
|
4328
5026
|
|
|
4329
5027
|
// src/traverse.ts
|
|
4330
|
-
var
|
|
5028
|
+
var import_types8 = require("@neat.is/types");
|
|
4331
5029
|
var ROOT_CAUSE_MAX_DEPTH = 5;
|
|
4332
5030
|
var BLAST_RADIUS_DEFAULT_DEPTH = 10;
|
|
4333
5031
|
function isFrontierNode(graph, nodeId) {
|
|
4334
5032
|
if (!graph.hasNode(nodeId)) return false;
|
|
4335
5033
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4336
|
-
return attrs.type ===
|
|
5034
|
+
return attrs.type === import_types8.NodeType.FrontierNode;
|
|
4337
5035
|
}
|
|
4338
5036
|
function resolveOwningService(graph, nodeId) {
|
|
4339
5037
|
if (!graph.hasNode(nodeId)) return null;
|
|
4340
5038
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4341
|
-
if (attrs.type ===
|
|
5039
|
+
if (attrs.type === import_types8.NodeType.ServiceNode) {
|
|
4342
5040
|
return { id: nodeId, svc: attrs };
|
|
4343
5041
|
}
|
|
4344
|
-
if (attrs.type ===
|
|
5042
|
+
if (attrs.type === import_types8.NodeType.FileNode || attrs.type === import_types8.NodeType.SymbolNode) {
|
|
4345
5043
|
for (const edgeId of graph.inboundEdges(nodeId)) {
|
|
4346
5044
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4347
|
-
if (e.type !==
|
|
5045
|
+
if (e.type !== import_types8.EdgeType.CONTAINS) continue;
|
|
4348
5046
|
const owner = graph.getNodeAttributes(e.source);
|
|
4349
|
-
if (owner.type ===
|
|
5047
|
+
if (owner.type === import_types8.NodeType.ServiceNode) {
|
|
4350
5048
|
return { id: e.source, svc: owner };
|
|
4351
5049
|
}
|
|
4352
|
-
if (owner.type ===
|
|
5050
|
+
if (owner.type === import_types8.NodeType.FileNode) {
|
|
4353
5051
|
return resolveOwningService(graph, e.source);
|
|
4354
5052
|
}
|
|
4355
5053
|
}
|
|
@@ -4362,7 +5060,7 @@ function bestEdgeBySource(graph, edgeIds) {
|
|
|
4362
5060
|
const e = graph.getEdgeAttributes(id);
|
|
4363
5061
|
if (isFrontierNode(graph, e.source)) continue;
|
|
4364
5062
|
const cur = best.get(e.source);
|
|
4365
|
-
if (!cur ||
|
|
5063
|
+
if (!cur || import_types8.PROV_RANK[e.provenance] > import_types8.PROV_RANK[cur.provenance]) {
|
|
4366
5064
|
best.set(e.source, e);
|
|
4367
5065
|
}
|
|
4368
5066
|
}
|
|
@@ -4374,7 +5072,7 @@ function bestEdgeByTarget(graph, edgeIds) {
|
|
|
4374
5072
|
const e = graph.getEdgeAttributes(id);
|
|
4375
5073
|
if (isFrontierNode(graph, e.target)) continue;
|
|
4376
5074
|
const cur = best.get(e.target);
|
|
4377
|
-
if (!cur ||
|
|
5075
|
+
if (!cur || import_types8.PROV_RANK[e.provenance] > import_types8.PROV_RANK[cur.provenance]) {
|
|
4378
5076
|
best.set(e.target, e);
|
|
4379
5077
|
}
|
|
4380
5078
|
}
|
|
@@ -4536,10 +5234,10 @@ function symbolRootCauseShape(graph, origin, walk6) {
|
|
|
4536
5234
|
return serviceRootCauseShape(graph, owner.svc, walk6);
|
|
4537
5235
|
}
|
|
4538
5236
|
var rootCauseShapes = {
|
|
4539
|
-
[
|
|
4540
|
-
[
|
|
4541
|
-
[
|
|
4542
|
-
[
|
|
5237
|
+
[import_types8.NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
5238
|
+
[import_types8.NodeType.ServiceNode]: serviceRootCauseShape,
|
|
5239
|
+
[import_types8.NodeType.FileNode]: fileRootCauseShape,
|
|
5240
|
+
[import_types8.NodeType.SymbolNode]: symbolRootCauseShape
|
|
4543
5241
|
};
|
|
4544
5242
|
function getRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
4545
5243
|
if (!graph.hasNode(errorNodeId)) return null;
|
|
@@ -4550,7 +5248,7 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
4550
5248
|
const match = shape(graph, origin, walk6);
|
|
4551
5249
|
if (match) {
|
|
4552
5250
|
const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
|
|
4553
|
-
return
|
|
5251
|
+
return import_types8.RootCauseResultSchema.parse({
|
|
4554
5252
|
rootCauseNode: match.rootCauseNode,
|
|
4555
5253
|
rootCauseReason: reason,
|
|
4556
5254
|
traversalPath: walk6.path,
|
|
@@ -4560,7 +5258,7 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
4560
5258
|
});
|
|
4561
5259
|
}
|
|
4562
5260
|
}
|
|
4563
|
-
if (origin.type ===
|
|
5261
|
+
if (origin.type === import_types8.NodeType.ServiceNode) {
|
|
4564
5262
|
const crossService = crossServiceRootCause(graph, errorNodeId, incidents, errorEvent);
|
|
4565
5263
|
if (crossService) return crossService;
|
|
4566
5264
|
}
|
|
@@ -4600,8 +5298,8 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
4600
5298
|
const loc = localizeFromIncidents(nodeId, incidents, errorEvent);
|
|
4601
5299
|
if (!loc) return null;
|
|
4602
5300
|
const traversalPath = loc.fileNode ? [nodeId, loc.fileNode] : [nodeId];
|
|
4603
|
-
const edgeProvenances = loc.fileNode ? [
|
|
4604
|
-
return
|
|
5301
|
+
const edgeProvenances = loc.fileNode ? [import_types8.Provenance.OBSERVED] : [];
|
|
5302
|
+
return import_types8.RootCauseResultSchema.parse({
|
|
4605
5303
|
rootCauseNode: loc.rootCauseNode,
|
|
4606
5304
|
rootCauseReason: loc.rootCauseReason,
|
|
4607
5305
|
traversalPath,
|
|
@@ -4611,21 +5309,21 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
4611
5309
|
});
|
|
4612
5310
|
}
|
|
4613
5311
|
function isFailingCallEdge(e) {
|
|
4614
|
-
return e.type ===
|
|
5312
|
+
return e.type === import_types8.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
4615
5313
|
}
|
|
4616
5314
|
function callSourcesForService(graph, serviceId7) {
|
|
4617
5315
|
const ids = [serviceId7];
|
|
4618
5316
|
for (const edgeId of graph.outboundEdges(serviceId7)) {
|
|
4619
5317
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4620
|
-
if (e.type !==
|
|
5318
|
+
if (e.type !== import_types8.EdgeType.CONTAINS) continue;
|
|
4621
5319
|
const tgt = graph.getNodeAttributes(e.target);
|
|
4622
|
-
if (tgt.type !==
|
|
5320
|
+
if (tgt.type !== import_types8.NodeType.FileNode) continue;
|
|
4623
5321
|
ids.push(e.target);
|
|
4624
5322
|
for (const symEdgeId of graph.outboundEdges(e.target)) {
|
|
4625
5323
|
const se = graph.getEdgeAttributes(symEdgeId);
|
|
4626
|
-
if (se.type !==
|
|
5324
|
+
if (se.type !== import_types8.EdgeType.CONTAINS) continue;
|
|
4627
5325
|
const sym = graph.getNodeAttributes(se.target);
|
|
4628
|
-
if (sym.type ===
|
|
5326
|
+
if (sym.type === import_types8.NodeType.SymbolNode) ids.push(se.target);
|
|
4629
5327
|
}
|
|
4630
5328
|
}
|
|
4631
5329
|
return ids;
|
|
@@ -4634,8 +5332,8 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
4634
5332
|
const ec = e.signal?.errorCount ?? 0;
|
|
4635
5333
|
const cc = curEdge.signal?.errorCount ?? 0;
|
|
4636
5334
|
if (ec !== cc) return ec > cc;
|
|
4637
|
-
if (
|
|
4638
|
-
return
|
|
5335
|
+
if (import_types8.PROV_RANK[e.provenance] !== import_types8.PROV_RANK[curEdge.provenance]) {
|
|
5336
|
+
return import_types8.PROV_RANK[e.provenance] > import_types8.PROV_RANK[curEdge.provenance];
|
|
4639
5337
|
}
|
|
4640
5338
|
return id < curId;
|
|
4641
5339
|
}
|
|
@@ -4684,10 +5382,10 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
4684
5382
|
let rootCauseNode = culprit;
|
|
4685
5383
|
if (loc.fileNode) {
|
|
4686
5384
|
path60.push(loc.fileNode);
|
|
4687
|
-
edgeProvenances.push(
|
|
5385
|
+
edgeProvenances.push(import_types8.Provenance.OBSERVED);
|
|
4688
5386
|
rootCauseNode = loc.fileNode;
|
|
4689
5387
|
}
|
|
4690
|
-
return
|
|
5388
|
+
return import_types8.RootCauseResultSchema.parse({
|
|
4691
5389
|
rootCauseNode,
|
|
4692
5390
|
rootCauseReason: loc.rootCauseReason,
|
|
4693
5391
|
traversalPath: path60,
|
|
@@ -4699,7 +5397,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
4699
5397
|
const lastEdge = chain.edges[chain.edges.length - 1];
|
|
4700
5398
|
const errs = lastEdge.signal?.errorCount ?? 0;
|
|
4701
5399
|
const culpritName = culprit.replace(/^service:/, "");
|
|
4702
|
-
return
|
|
5400
|
+
return import_types8.RootCauseResultSchema.parse({
|
|
4703
5401
|
rootCauseNode: culprit,
|
|
4704
5402
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
4705
5403
|
traversalPath: path60,
|
|
@@ -4710,7 +5408,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
4710
5408
|
}
|
|
4711
5409
|
function getBlastRadius(graph, nodeId, maxDepth = BLAST_RADIUS_DEFAULT_DEPTH) {
|
|
4712
5410
|
if (!graph.hasNode(nodeId)) {
|
|
4713
|
-
return
|
|
5411
|
+
return import_types8.BlastRadiusResultSchema.parse({ origin: nodeId, affectedNodes: [], totalAffected: 0 });
|
|
4714
5412
|
}
|
|
4715
5413
|
const seen = /* @__PURE__ */ new Map();
|
|
4716
5414
|
const queue = [{ nodeId, distance: 0, path: [nodeId], pathEdges: [] }];
|
|
@@ -4743,7 +5441,7 @@ function getBlastRadius(graph, nodeId, maxDepth = BLAST_RADIUS_DEFAULT_DEPTH) {
|
|
|
4743
5441
|
const affectedNodes = [...seen.values()].sort(
|
|
4744
5442
|
(a, b) => a.distance - b.distance || a.nodeId.localeCompare(b.nodeId)
|
|
4745
5443
|
);
|
|
4746
|
-
return
|
|
5444
|
+
return import_types8.BlastRadiusResultSchema.parse({
|
|
4747
5445
|
origin: nodeId,
|
|
4748
5446
|
affectedNodes,
|
|
4749
5447
|
totalAffected: affectedNodes.length
|
|
@@ -4753,7 +5451,7 @@ var TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH = 3;
|
|
|
4753
5451
|
var TRANSITIVE_DEPENDENCIES_MAX_DEPTH = 10;
|
|
4754
5452
|
function getTransitiveDependencies(graph, nodeId, depth = TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH) {
|
|
4755
5453
|
if (!graph.hasNode(nodeId)) {
|
|
4756
|
-
return
|
|
5454
|
+
return import_types8.TransitiveDependenciesResultSchema.parse({
|
|
4757
5455
|
origin: nodeId,
|
|
4758
5456
|
depth,
|
|
4759
5457
|
dependencies: [],
|
|
@@ -4765,7 +5463,7 @@ function getTransitiveDependencies(graph, nodeId, depth = TRANSITIVE_DEPENDENCIE
|
|
|
4765
5463
|
const enqueued = /* @__PURE__ */ new Set([nodeId]);
|
|
4766
5464
|
while (queue.length > 0) {
|
|
4767
5465
|
const frame = queue.shift();
|
|
4768
|
-
if (frame.distance > 0 && frame.edge && frame.edge.type !==
|
|
5466
|
+
if (frame.distance > 0 && frame.edge && frame.edge.type !== import_types8.EdgeType.CONTAINS) {
|
|
4769
5467
|
seen.set(frame.nodeId, {
|
|
4770
5468
|
nodeId: frame.nodeId,
|
|
4771
5469
|
distance: frame.distance,
|
|
@@ -4784,7 +5482,7 @@ function getTransitiveDependencies(graph, nodeId, depth = TRANSITIVE_DEPENDENCIE
|
|
|
4784
5482
|
const dependencies = [...seen.values()].sort(
|
|
4785
5483
|
(a, b) => a.distance - b.distance || a.nodeId.localeCompare(b.nodeId)
|
|
4786
5484
|
);
|
|
4787
|
-
return
|
|
5485
|
+
return import_types8.TransitiveDependenciesResultSchema.parse({
|
|
4788
5486
|
origin: nodeId,
|
|
4789
5487
|
depth,
|
|
4790
5488
|
dependencies,
|
|
@@ -4793,7 +5491,7 @@ function getTransitiveDependencies(graph, nodeId, depth = TRANSITIVE_DEPENDENCIE
|
|
|
4793
5491
|
}
|
|
4794
5492
|
function getObservedDependencies(graph, nodeId) {
|
|
4795
5493
|
if (!graph.hasNode(nodeId)) {
|
|
4796
|
-
return
|
|
5494
|
+
return import_types8.ObservedDependenciesResultSchema.parse({
|
|
4797
5495
|
origin: nodeId,
|
|
4798
5496
|
dependencies: [],
|
|
4799
5497
|
observed: false,
|
|
@@ -4803,12 +5501,12 @@ function getObservedDependencies(graph, nodeId) {
|
|
|
4803
5501
|
}
|
|
4804
5502
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4805
5503
|
const scope = [nodeId];
|
|
4806
|
-
if (attrs.type ===
|
|
5504
|
+
if (attrs.type === import_types8.NodeType.ServiceNode) {
|
|
4807
5505
|
for (const edgeId of graph.outboundEdges(nodeId)) {
|
|
4808
5506
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4809
|
-
if (e.type !==
|
|
5507
|
+
if (e.type !== import_types8.EdgeType.CONTAINS) continue;
|
|
4810
5508
|
const owned = graph.getNodeAttributes(e.target);
|
|
4811
|
-
if (owned.type ===
|
|
5509
|
+
if (owned.type === import_types8.NodeType.FileNode) scope.push(e.target);
|
|
4812
5510
|
}
|
|
4813
5511
|
}
|
|
4814
5512
|
const dependencies = [];
|
|
@@ -4817,13 +5515,13 @@ function getObservedDependencies(graph, nodeId) {
|
|
|
4817
5515
|
for (const src of scope) {
|
|
4818
5516
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
4819
5517
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4820
|
-
if (e.type ===
|
|
4821
|
-
if (e.provenance ===
|
|
5518
|
+
if (e.type === import_types8.EdgeType.CONTAINS) continue;
|
|
5519
|
+
if (e.provenance === import_types8.Provenance.OBSERVED) {
|
|
4822
5520
|
if (!seenEdge.has(e.id)) {
|
|
4823
5521
|
seenEdge.add(e.id);
|
|
4824
5522
|
dependencies.push(e);
|
|
4825
5523
|
}
|
|
4826
|
-
} else if (e.provenance ===
|
|
5524
|
+
} else if (e.provenance === import_types8.Provenance.EXTRACTED) {
|
|
4827
5525
|
hasExtractedOutbound = true;
|
|
4828
5526
|
}
|
|
4829
5527
|
}
|
|
@@ -4832,14 +5530,14 @@ function getObservedDependencies(graph, nodeId) {
|
|
|
4832
5530
|
for (const tgt of scope) {
|
|
4833
5531
|
for (const edgeId of graph.inboundEdges(tgt)) {
|
|
4834
5532
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4835
|
-
if (e.type ===
|
|
4836
|
-
if (e.provenance ===
|
|
5533
|
+
if (e.type === import_types8.EdgeType.CONTAINS) continue;
|
|
5534
|
+
if (e.provenance === import_types8.Provenance.OBSERVED) inboundObservedCount += 1;
|
|
4837
5535
|
}
|
|
4838
5536
|
}
|
|
4839
5537
|
dependencies.sort(
|
|
4840
5538
|
(a, b) => a.target.localeCompare(b.target) || a.source.localeCompare(b.source) || a.id.localeCompare(b.id)
|
|
4841
5539
|
);
|
|
4842
|
-
return
|
|
5540
|
+
return import_types8.ObservedDependenciesResultSchema.parse({
|
|
4843
5541
|
origin: nodeId,
|
|
4844
5542
|
dependencies,
|
|
4845
5543
|
observed: dependencies.length > 0 || inboundObservedCount > 0,
|
|
@@ -4853,34 +5551,34 @@ function bucketKey(source, target, type) {
|
|
|
4853
5551
|
return `${type}|${source}|${target}`;
|
|
4854
5552
|
}
|
|
4855
5553
|
function bucketSourceFor(graph, edge) {
|
|
4856
|
-
if (edge.type !==
|
|
4857
|
-
const parsed = (0,
|
|
5554
|
+
if (edge.type !== import_types9.EdgeType.CONNECTS_TO) return edge.source;
|
|
5555
|
+
const parsed = (0, import_types9.parseFileId)(edge.source);
|
|
4858
5556
|
if (!parsed || !graph.hasNode(edge.target)) return edge.source;
|
|
4859
5557
|
const target = graph.getNodeAttributes(edge.target);
|
|
4860
|
-
if (target.type !==
|
|
4861
|
-
return (0,
|
|
5558
|
+
if (target.type !== import_types9.NodeType.DatabaseNode) return edge.source;
|
|
5559
|
+
return (0, import_types9.serviceId)(parsed.service);
|
|
4862
5560
|
}
|
|
4863
5561
|
function bucketEdges(graph) {
|
|
4864
5562
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
4865
5563
|
graph.forEachEdge((id, attrs) => {
|
|
4866
5564
|
const e = attrs;
|
|
4867
|
-
const parsed = (0,
|
|
5565
|
+
const parsed = (0, import_types9.parseEdgeId)(id);
|
|
4868
5566
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
4869
5567
|
const source = bucketSourceFor(graph, e);
|
|
4870
5568
|
const key = bucketKey(source, e.target, e.type);
|
|
4871
5569
|
const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
|
|
4872
5570
|
switch (provenance) {
|
|
4873
|
-
case
|
|
5571
|
+
case import_types9.Provenance.EXTRACTED:
|
|
4874
5572
|
cur.extracted = e;
|
|
4875
5573
|
break;
|
|
4876
|
-
case
|
|
5574
|
+
case import_types9.Provenance.OBSERVED:
|
|
4877
5575
|
cur.observed = e;
|
|
4878
5576
|
break;
|
|
4879
|
-
case
|
|
5577
|
+
case import_types9.Provenance.INFERRED:
|
|
4880
5578
|
cur.inferred = e;
|
|
4881
5579
|
break;
|
|
4882
5580
|
default:
|
|
4883
|
-
if (e.provenance ===
|
|
5581
|
+
if (e.provenance === import_types9.Provenance.STALE) cur.stale = e;
|
|
4884
5582
|
}
|
|
4885
5583
|
buckets2.set(key, cur);
|
|
4886
5584
|
});
|
|
@@ -4889,17 +5587,17 @@ function bucketEdges(graph) {
|
|
|
4889
5587
|
function nodeIsFrontier(graph, nodeId) {
|
|
4890
5588
|
if (!graph.hasNode(nodeId)) return false;
|
|
4891
5589
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4892
|
-
return attrs.type ===
|
|
5590
|
+
return attrs.type === import_types9.NodeType.FrontierNode;
|
|
4893
5591
|
}
|
|
4894
5592
|
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
4895
5593
|
if (!graph.hasNode(nodeId)) return false;
|
|
4896
5594
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4897
|
-
return attrs.type ===
|
|
5595
|
+
return attrs.type === import_types9.NodeType.WebSocketChannelNode;
|
|
4898
5596
|
}
|
|
4899
5597
|
function nodeIsSymbol(graph, nodeId) {
|
|
4900
5598
|
if (!graph.hasNode(nodeId)) return false;
|
|
4901
5599
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4902
|
-
return attrs.type ===
|
|
5600
|
+
return attrs.type === import_types9.NodeType.SymbolNode;
|
|
4903
5601
|
}
|
|
4904
5602
|
function clampConfidence(n) {
|
|
4905
5603
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -4919,14 +5617,14 @@ function gradedConfidence(edge) {
|
|
|
4919
5617
|
return clampConfidence(confidenceForEdge(edge));
|
|
4920
5618
|
}
|
|
4921
5619
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
5620
|
+
import_types9.EdgeType.CALLS,
|
|
5621
|
+
import_types9.EdgeType.CONNECTS_TO,
|
|
5622
|
+
import_types9.EdgeType.PUBLISHES_TO,
|
|
5623
|
+
import_types9.EdgeType.CONSUMES_FROM
|
|
4926
5624
|
]);
|
|
4927
5625
|
function detectMissingDivergences(graph, bucket) {
|
|
4928
5626
|
const out = [];
|
|
4929
|
-
if (bucket.type ===
|
|
5627
|
+
if (bucket.type === import_types9.EdgeType.CONTAINS) return out;
|
|
4930
5628
|
if (nodeIsSymbol(graph, bucket.source) || nodeIsSymbol(graph, bucket.target)) return out;
|
|
4931
5629
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
4932
5630
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
@@ -4968,7 +5666,7 @@ function declaredHostFor(svc) {
|
|
|
4968
5666
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4969
5667
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4970
5668
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4971
|
-
if (e.type ===
|
|
5669
|
+
if (e.type === import_types9.EdgeType.CONFIGURED_BY && e.provenance === import_types9.Provenance.EXTRACTED) {
|
|
4972
5670
|
return true;
|
|
4973
5671
|
}
|
|
4974
5672
|
}
|
|
@@ -4981,10 +5679,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4981
5679
|
const out = [];
|
|
4982
5680
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4983
5681
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4984
|
-
if (edge.type !==
|
|
4985
|
-
if (edge.provenance !==
|
|
5682
|
+
if (edge.type !== import_types9.EdgeType.CONNECTS_TO) continue;
|
|
5683
|
+
if (edge.provenance !== import_types9.Provenance.OBSERVED) continue;
|
|
4986
5684
|
const target = graph.getNodeAttributes(edge.target);
|
|
4987
|
-
if (target.type !==
|
|
5685
|
+
if (target.type !== import_types9.NodeType.DatabaseNode) continue;
|
|
4988
5686
|
const observedHost = target.host?.trim();
|
|
4989
5687
|
if (!observedHost) continue;
|
|
4990
5688
|
if (observedHost === declaredHost) continue;
|
|
@@ -5006,10 +5704,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
5006
5704
|
const deps = svc.dependencies ?? {};
|
|
5007
5705
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
5008
5706
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
5009
|
-
if (edge.type !==
|
|
5010
|
-
if (edge.provenance !==
|
|
5707
|
+
if (edge.type !== import_types9.EdgeType.CONNECTS_TO) continue;
|
|
5708
|
+
if (edge.provenance !== import_types9.Provenance.OBSERVED) continue;
|
|
5011
5709
|
const target = graph.getNodeAttributes(edge.target);
|
|
5012
|
-
if (target.type !==
|
|
5710
|
+
if (target.type !== import_types9.NodeType.DatabaseNode) continue;
|
|
5013
5711
|
for (const pair of compatPairs()) {
|
|
5014
5712
|
if (pair.engine !== target.engine) continue;
|
|
5015
5713
|
const declared = deps[pair.driver];
|
|
@@ -5106,7 +5804,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
5106
5804
|
for (const d of all) {
|
|
5107
5805
|
if (d.type !== "host-mismatch") continue;
|
|
5108
5806
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
5109
|
-
declaredHalf.add((0,
|
|
5807
|
+
declaredHalf.add((0, import_types9.databaseId)(d.extractedHost));
|
|
5110
5808
|
}
|
|
5111
5809
|
if (observedHalf.size === 0) return all;
|
|
5112
5810
|
return all.filter((d) => {
|
|
@@ -5125,13 +5823,13 @@ function computeDivergences(graph, opts = {}) {
|
|
|
5125
5823
|
}
|
|
5126
5824
|
graph.forEachNode((nodeId, attrs) => {
|
|
5127
5825
|
const n = attrs;
|
|
5128
|
-
if (n.type ===
|
|
5826
|
+
if (n.type === import_types9.NodeType.ServiceNode) {
|
|
5129
5827
|
const svc = n;
|
|
5130
5828
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
5131
5829
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
5132
5830
|
return;
|
|
5133
5831
|
}
|
|
5134
|
-
if (n.type ===
|
|
5832
|
+
if (n.type === import_types9.NodeType.InfraNode && n.kind === "sql-table") {
|
|
5135
5833
|
for (const d of detectColumnDrift(n)) all.push(d);
|
|
5136
5834
|
}
|
|
5137
5835
|
});
|
|
@@ -5167,7 +5865,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
5167
5865
|
const bc = "column" in b && b.column ? b.column : "";
|
|
5168
5866
|
return ac.localeCompare(bc);
|
|
5169
5867
|
});
|
|
5170
|
-
return
|
|
5868
|
+
return import_types9.DivergenceResultSchema.parse({
|
|
5171
5869
|
divergences: filtered,
|
|
5172
5870
|
totalAffected: filtered.length,
|
|
5173
5871
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5227,16 +5925,16 @@ init_cjs_shims();
|
|
|
5227
5925
|
|
|
5228
5926
|
// src/extract/services.ts
|
|
5229
5927
|
init_cjs_shims();
|
|
5230
|
-
var
|
|
5231
|
-
var
|
|
5928
|
+
var import_node_fs13 = require("fs");
|
|
5929
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
5232
5930
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
5233
5931
|
var import_minimatch2 = require("minimatch");
|
|
5234
|
-
var
|
|
5932
|
+
var import_types11 = require("@neat.is/types");
|
|
5235
5933
|
|
|
5236
5934
|
// src/extract/python.ts
|
|
5237
5935
|
init_cjs_shims();
|
|
5238
|
-
var
|
|
5239
|
-
var
|
|
5936
|
+
var import_node_fs10 = require("fs");
|
|
5937
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
5240
5938
|
var import_smol_toml = require("smol-toml");
|
|
5241
5939
|
var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
|
|
5242
5940
|
function parseRequirementsTxt(content) {
|
|
@@ -5269,25 +5967,25 @@ function depsFromPyProject(pyproject) {
|
|
|
5269
5967
|
return out;
|
|
5270
5968
|
}
|
|
5271
5969
|
async function discoverPythonService(serviceDir) {
|
|
5272
|
-
const pyprojectPath =
|
|
5273
|
-
const requirementsPath =
|
|
5274
|
-
const setupPath =
|
|
5970
|
+
const pyprojectPath = import_node_path11.default.join(serviceDir, "pyproject.toml");
|
|
5971
|
+
const requirementsPath = import_node_path11.default.join(serviceDir, "requirements.txt");
|
|
5972
|
+
const setupPath = import_node_path11.default.join(serviceDir, "setup.py");
|
|
5275
5973
|
const hasPyproject = await exists2(pyprojectPath);
|
|
5276
5974
|
const hasRequirements = await exists2(requirementsPath);
|
|
5277
5975
|
const hasSetup = await exists2(setupPath);
|
|
5278
5976
|
if (!hasPyproject && !hasRequirements && !hasSetup) return null;
|
|
5279
|
-
let name =
|
|
5977
|
+
let name = import_node_path11.default.basename(serviceDir);
|
|
5280
5978
|
let version;
|
|
5281
5979
|
const dependencies = {};
|
|
5282
5980
|
if (hasPyproject) {
|
|
5283
|
-
const raw = await
|
|
5981
|
+
const raw = await import_node_fs10.promises.readFile(pyprojectPath, "utf8");
|
|
5284
5982
|
const pyproject = (0, import_smol_toml.parse)(raw);
|
|
5285
5983
|
name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
|
|
5286
5984
|
version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
|
|
5287
5985
|
Object.assign(dependencies, depsFromPyProject(pyproject));
|
|
5288
5986
|
}
|
|
5289
5987
|
if (hasRequirements) {
|
|
5290
|
-
const raw = await
|
|
5988
|
+
const raw = await import_node_fs10.promises.readFile(requirementsPath, "utf8");
|
|
5291
5989
|
Object.assign(dependencies, parseRequirementsTxt(raw));
|
|
5292
5990
|
}
|
|
5293
5991
|
return { name, version, dependencies };
|
|
@@ -5302,9 +6000,9 @@ function pythonToPackage(service) {
|
|
|
5302
6000
|
|
|
5303
6001
|
// src/extract/go.ts
|
|
5304
6002
|
init_cjs_shims();
|
|
5305
|
-
var
|
|
5306
|
-
var
|
|
5307
|
-
var
|
|
6003
|
+
var import_node_fs11 = require("fs");
|
|
6004
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
6005
|
+
var import_types10 = require("@neat.is/types");
|
|
5308
6006
|
function parseGoMod(source) {
|
|
5309
6007
|
const module2 = source.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
|
|
5310
6008
|
if (!module2) return null;
|
|
@@ -5323,7 +6021,7 @@ function parseGoMod(source) {
|
|
|
5323
6021
|
async function discoverGoService(scanPath, dir) {
|
|
5324
6022
|
let raw;
|
|
5325
6023
|
try {
|
|
5326
|
-
raw = await
|
|
6024
|
+
raw = await import_node_fs11.promises.readFile(import_node_path12.default.join(dir, "go.mod"), "utf8");
|
|
5327
6025
|
} catch {
|
|
5328
6026
|
return null;
|
|
5329
6027
|
}
|
|
@@ -5332,12 +6030,12 @@ async function discoverGoService(scanPath, dir) {
|
|
|
5332
6030
|
const name = mod.module.split("/").filter(Boolean).pop() ?? mod.module;
|
|
5333
6031
|
const pkg = { name, dependencies: mod.dependencies };
|
|
5334
6032
|
const node = {
|
|
5335
|
-
id: (0,
|
|
5336
|
-
type:
|
|
6033
|
+
id: (0, import_types10.serviceId)(name),
|
|
6034
|
+
type: import_types10.NodeType.ServiceNode,
|
|
5337
6035
|
name,
|
|
5338
6036
|
language: "go",
|
|
5339
6037
|
dependencies: mod.dependencies,
|
|
5340
|
-
repoPath:
|
|
6038
|
+
repoPath: import_node_path12.default.relative(scanPath, dir),
|
|
5341
6039
|
...mod.dependencies["github.com/gin-gonic/gin"] ? { framework: "gin" } : {}
|
|
5342
6040
|
};
|
|
5343
6041
|
return { pkg, dir, node };
|
|
@@ -5345,17 +6043,17 @@ async function discoverGoService(scanPath, dir) {
|
|
|
5345
6043
|
|
|
5346
6044
|
// src/extract/owners.ts
|
|
5347
6045
|
init_cjs_shims();
|
|
5348
|
-
var
|
|
5349
|
-
var
|
|
6046
|
+
var import_node_fs12 = require("fs");
|
|
6047
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
5350
6048
|
var import_minimatch = require("minimatch");
|
|
5351
6049
|
async function loadCodeowners(scanPath) {
|
|
5352
6050
|
const candidates = [
|
|
5353
|
-
|
|
5354
|
-
|
|
6051
|
+
import_node_path13.default.join(scanPath, "CODEOWNERS"),
|
|
6052
|
+
import_node_path13.default.join(scanPath, ".github", "CODEOWNERS")
|
|
5355
6053
|
];
|
|
5356
6054
|
for (const file of candidates) {
|
|
5357
6055
|
if (await exists2(file)) {
|
|
5358
|
-
const raw = await
|
|
6056
|
+
const raw = await import_node_fs12.promises.readFile(file, "utf8");
|
|
5359
6057
|
return parseCodeowners(raw);
|
|
5360
6058
|
}
|
|
5361
6059
|
}
|
|
@@ -5373,7 +6071,7 @@ function parseCodeowners(raw) {
|
|
|
5373
6071
|
return { rules };
|
|
5374
6072
|
}
|
|
5375
6073
|
function matchOwner(file, repoPath) {
|
|
5376
|
-
const normalized = repoPath.split(
|
|
6074
|
+
const normalized = repoPath.split(import_node_path13.default.sep).join("/");
|
|
5377
6075
|
for (const rule of file.rules) {
|
|
5378
6076
|
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
5379
6077
|
}
|
|
@@ -5389,7 +6087,7 @@ function matchesPattern(rawPattern, repoPath) {
|
|
|
5389
6087
|
return false;
|
|
5390
6088
|
}
|
|
5391
6089
|
async function readPackageJsonAuthor(serviceDir) {
|
|
5392
|
-
const pkgPath =
|
|
6090
|
+
const pkgPath = import_node_path13.default.join(serviceDir, "package.json");
|
|
5393
6091
|
if (!await exists2(pkgPath)) return null;
|
|
5394
6092
|
try {
|
|
5395
6093
|
const pkg = await readJson(pkgPath);
|
|
@@ -5426,27 +6124,27 @@ function workspaceGlobs(pkg) {
|
|
|
5426
6124
|
return null;
|
|
5427
6125
|
}
|
|
5428
6126
|
async function hasPythonManifest(dir) {
|
|
5429
|
-
return await exists2(
|
|
6127
|
+
return await exists2(import_node_path14.default.join(dir, "pyproject.toml")) || await exists2(import_node_path14.default.join(dir, "requirements.txt")) || await exists2(import_node_path14.default.join(dir, "setup.py"));
|
|
5430
6128
|
}
|
|
5431
6129
|
async function hasGoManifest(dir) {
|
|
5432
|
-
return exists2(
|
|
6130
|
+
return exists2(import_node_path14.default.join(dir, "go.mod"));
|
|
5433
6131
|
}
|
|
5434
6132
|
async function loadGitignore(scanPath) {
|
|
5435
|
-
const gitignorePath =
|
|
6133
|
+
const gitignorePath = import_node_path14.default.join(scanPath, ".gitignore");
|
|
5436
6134
|
if (!await exists2(gitignorePath)) return null;
|
|
5437
|
-
const raw = await
|
|
6135
|
+
const raw = await import_node_fs13.promises.readFile(gitignorePath, "utf8");
|
|
5438
6136
|
return (0, import_ignore.default)().add(raw);
|
|
5439
6137
|
}
|
|
5440
6138
|
async function walkDirs(start, scanPath, options, visit) {
|
|
5441
6139
|
async function recurse(current, depth) {
|
|
5442
6140
|
if (depth > options.maxDepth) return;
|
|
5443
|
-
const entries = await
|
|
6141
|
+
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
5444
6142
|
for (const entry of entries) {
|
|
5445
6143
|
if (!entry.isDirectory()) continue;
|
|
5446
6144
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5447
|
-
const child =
|
|
6145
|
+
const child = import_node_path14.default.join(current, entry.name);
|
|
5448
6146
|
if (options.ig) {
|
|
5449
|
-
const rel =
|
|
6147
|
+
const rel = import_node_path14.default.relative(scanPath, child).split(import_node_path14.default.sep).join("/");
|
|
5450
6148
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
5451
6149
|
}
|
|
5452
6150
|
if (await isPythonVenvDir(child)) continue;
|
|
@@ -5462,8 +6160,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
5462
6160
|
for (const raw of globs) {
|
|
5463
6161
|
const pattern = raw.replace(/^\.\//, "");
|
|
5464
6162
|
if (!pattern.includes("*")) {
|
|
5465
|
-
const candidate =
|
|
5466
|
-
if (await exists2(
|
|
6163
|
+
const candidate = import_node_path14.default.join(scanPath, pattern);
|
|
6164
|
+
if (await exists2(import_node_path14.default.join(candidate, "package.json"))) found.add(candidate);
|
|
5467
6165
|
continue;
|
|
5468
6166
|
}
|
|
5469
6167
|
const segments = pattern.split("/");
|
|
@@ -5472,13 +6170,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
5472
6170
|
if (seg.includes("*")) break;
|
|
5473
6171
|
staticSegments.push(seg);
|
|
5474
6172
|
}
|
|
5475
|
-
const start =
|
|
6173
|
+
const start = import_node_path14.default.join(scanPath, ...staticSegments);
|
|
5476
6174
|
if (!await exists2(start)) continue;
|
|
5477
6175
|
const hasDoubleStar = pattern.includes("**");
|
|
5478
6176
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
5479
6177
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
5480
|
-
const rel =
|
|
5481
|
-
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists2(
|
|
6178
|
+
const rel = import_node_path14.default.relative(scanPath, dir).split(import_node_path14.default.sep).join("/");
|
|
6179
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists2(import_node_path14.default.join(dir, "package.json"))) {
|
|
5482
6180
|
found.add(dir);
|
|
5483
6181
|
}
|
|
5484
6182
|
});
|
|
@@ -5501,31 +6199,31 @@ function detectJsFramework(pkg) {
|
|
|
5501
6199
|
async function detectJsServiceLanguage(dir, pkg) {
|
|
5502
6200
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5503
6201
|
if (deps["typescript"] !== void 0) return "typescript";
|
|
5504
|
-
const entries = await
|
|
6202
|
+
const entries = await import_node_fs13.promises.readdir(dir).catch(() => []);
|
|
5505
6203
|
if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
|
|
5506
6204
|
return "javascript";
|
|
5507
6205
|
}
|
|
5508
6206
|
async function discoverNodeService(scanPath, dir) {
|
|
5509
|
-
const pkgPath =
|
|
6207
|
+
const pkgPath = import_node_path14.default.join(dir, "package.json");
|
|
5510
6208
|
if (!await exists2(pkgPath)) return null;
|
|
5511
6209
|
let pkg;
|
|
5512
6210
|
try {
|
|
5513
6211
|
pkg = await readJson(pkgPath);
|
|
5514
6212
|
} catch (err) {
|
|
5515
|
-
recordExtractionError("services",
|
|
6213
|
+
recordExtractionError("services", import_node_path14.default.relative(scanPath, pkgPath), err);
|
|
5516
6214
|
return null;
|
|
5517
6215
|
}
|
|
5518
6216
|
if (!pkg.name) return null;
|
|
5519
6217
|
const framework = detectJsFramework(pkg);
|
|
5520
6218
|
const language = await detectJsServiceLanguage(dir, pkg);
|
|
5521
6219
|
const node = {
|
|
5522
|
-
id: (0,
|
|
5523
|
-
type:
|
|
6220
|
+
id: (0, import_types11.serviceId)(pkg.name),
|
|
6221
|
+
type: import_types11.NodeType.ServiceNode,
|
|
5524
6222
|
name: pkg.name,
|
|
5525
6223
|
language,
|
|
5526
6224
|
version: pkg.version,
|
|
5527
6225
|
dependencies: pkg.dependencies ?? {},
|
|
5528
|
-
repoPath:
|
|
6226
|
+
repoPath: import_node_path14.default.relative(scanPath, dir),
|
|
5529
6227
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
|
|
5530
6228
|
...framework ? { framework } : {}
|
|
5531
6229
|
};
|
|
@@ -5536,18 +6234,18 @@ async function discoverPyService(scanPath, dir) {
|
|
|
5536
6234
|
if (!py) return null;
|
|
5537
6235
|
const pkg = pythonToPackage(py);
|
|
5538
6236
|
const node = {
|
|
5539
|
-
id: (0,
|
|
5540
|
-
type:
|
|
6237
|
+
id: (0, import_types11.serviceId)(py.name),
|
|
6238
|
+
type: import_types11.NodeType.ServiceNode,
|
|
5541
6239
|
name: py.name,
|
|
5542
6240
|
language: "python",
|
|
5543
6241
|
version: py.version,
|
|
5544
6242
|
dependencies: py.dependencies,
|
|
5545
|
-
repoPath:
|
|
6243
|
+
repoPath: import_node_path14.default.relative(scanPath, dir)
|
|
5546
6244
|
};
|
|
5547
6245
|
return { pkg, dir, node };
|
|
5548
6246
|
}
|
|
5549
6247
|
async function discoverServices(scanPath) {
|
|
5550
|
-
const rootPkgPath =
|
|
6248
|
+
const rootPkgPath = import_node_path14.default.join(scanPath, "package.json");
|
|
5551
6249
|
let rootPkg = null;
|
|
5552
6250
|
if (await exists2(rootPkgPath)) {
|
|
5553
6251
|
try {
|
|
@@ -5555,7 +6253,7 @@ async function discoverServices(scanPath) {
|
|
|
5555
6253
|
} catch (err) {
|
|
5556
6254
|
recordExtractionError(
|
|
5557
6255
|
"services workspaces",
|
|
5558
|
-
|
|
6256
|
+
import_node_path14.default.relative(scanPath, rootPkgPath),
|
|
5559
6257
|
err
|
|
5560
6258
|
);
|
|
5561
6259
|
}
|
|
@@ -5576,7 +6274,7 @@ async function discoverServices(scanPath) {
|
|
|
5576
6274
|
scanPath,
|
|
5577
6275
|
{ maxDepth: parseScanDepth(), ig },
|
|
5578
6276
|
async (dir) => {
|
|
5579
|
-
if (await exists2(
|
|
6277
|
+
if (await exists2(import_node_path14.default.join(dir, "package.json"))) {
|
|
5580
6278
|
candidateDirs.push(dir);
|
|
5581
6279
|
} else if (await hasPythonManifest(dir) || await hasGoManifest(dir)) {
|
|
5582
6280
|
candidateDirs.push(dir);
|
|
@@ -5592,8 +6290,8 @@ async function discoverServices(scanPath) {
|
|
|
5592
6290
|
if (!service) continue;
|
|
5593
6291
|
const existingDir = seen.get(service.node.name);
|
|
5594
6292
|
if (existingDir !== void 0) {
|
|
5595
|
-
const a =
|
|
5596
|
-
const b =
|
|
6293
|
+
const a = import_node_path14.default.relative(scanPath, existingDir) || ".";
|
|
6294
|
+
const b = import_node_path14.default.relative(scanPath, dir) || ".";
|
|
5597
6295
|
console.warn(
|
|
5598
6296
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
5599
6297
|
);
|
|
@@ -5630,10 +6328,10 @@ function addServiceNodes(graph, services) {
|
|
|
5630
6328
|
|
|
5631
6329
|
// src/extract/aliases.ts
|
|
5632
6330
|
init_cjs_shims();
|
|
5633
|
-
var
|
|
5634
|
-
var
|
|
6331
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
6332
|
+
var import_node_fs14 = require("fs");
|
|
5635
6333
|
var import_yaml2 = require("yaml");
|
|
5636
|
-
var
|
|
6334
|
+
var import_types12 = require("@neat.is/types");
|
|
5637
6335
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
5638
6336
|
"Service",
|
|
5639
6337
|
"Deployment",
|
|
@@ -5643,7 +6341,7 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
5643
6341
|
function addAliases(graph, serviceId7, candidates) {
|
|
5644
6342
|
if (!graph.hasNode(serviceId7)) return;
|
|
5645
6343
|
const node = graph.getNodeAttributes(serviceId7);
|
|
5646
|
-
if (node.type !==
|
|
6344
|
+
if (node.type !== import_types12.NodeType.ServiceNode) return;
|
|
5647
6345
|
const set = new Set(node.aliases ?? []);
|
|
5648
6346
|
for (const c of candidates) {
|
|
5649
6347
|
if (!c) continue;
|
|
@@ -5658,14 +6356,14 @@ function indexServicesByName(services) {
|
|
|
5658
6356
|
const map = /* @__PURE__ */ new Map();
|
|
5659
6357
|
for (const s of services) {
|
|
5660
6358
|
map.set(s.node.name, s.node.id);
|
|
5661
|
-
map.set(
|
|
6359
|
+
map.set(import_node_path15.default.basename(s.dir), s.node.id);
|
|
5662
6360
|
}
|
|
5663
6361
|
return map;
|
|
5664
6362
|
}
|
|
5665
6363
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
5666
6364
|
let composePath = null;
|
|
5667
6365
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
5668
|
-
const abs =
|
|
6366
|
+
const abs = import_node_path15.default.join(scanPath, name);
|
|
5669
6367
|
if (await exists2(abs)) {
|
|
5670
6368
|
composePath = abs;
|
|
5671
6369
|
break;
|
|
@@ -5678,7 +6376,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
5678
6376
|
} catch (err) {
|
|
5679
6377
|
recordExtractionError(
|
|
5680
6378
|
"aliases compose",
|
|
5681
|
-
|
|
6379
|
+
import_node_path15.default.relative(scanPath, composePath),
|
|
5682
6380
|
err
|
|
5683
6381
|
);
|
|
5684
6382
|
return;
|
|
@@ -5721,11 +6419,11 @@ function parseDockerfileLabels(content) {
|
|
|
5721
6419
|
}
|
|
5722
6420
|
async function collectDockerfileAliases(graph, services) {
|
|
5723
6421
|
for (const service of services) {
|
|
5724
|
-
const dockerfilePath =
|
|
6422
|
+
const dockerfilePath = import_node_path15.default.join(service.dir, "Dockerfile");
|
|
5725
6423
|
if (!await exists2(dockerfilePath)) continue;
|
|
5726
6424
|
let content;
|
|
5727
6425
|
try {
|
|
5728
|
-
content = await
|
|
6426
|
+
content = await import_node_fs14.promises.readFile(dockerfilePath, "utf8");
|
|
5729
6427
|
} catch (err) {
|
|
5730
6428
|
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
5731
6429
|
continue;
|
|
@@ -5737,15 +6435,15 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
5737
6435
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
5738
6436
|
if (depth > max) return [];
|
|
5739
6437
|
const out = [];
|
|
5740
|
-
const entries = await
|
|
6438
|
+
const entries = await import_node_fs14.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
5741
6439
|
for (const entry of entries) {
|
|
5742
6440
|
if (entry.isDirectory()) {
|
|
5743
6441
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5744
|
-
const child =
|
|
6442
|
+
const child = import_node_path15.default.join(start, entry.name);
|
|
5745
6443
|
if (await isPythonVenvDir(child)) continue;
|
|
5746
6444
|
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
5747
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
5748
|
-
out.push(
|
|
6445
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path15.default.extname(entry.name))) {
|
|
6446
|
+
out.push(import_node_path15.default.join(start, entry.name));
|
|
5749
6447
|
}
|
|
5750
6448
|
}
|
|
5751
6449
|
return out;
|
|
@@ -5772,614 +6470,230 @@ function k8sServiceTarget(doc, byName) {
|
|
|
5772
6470
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
5773
6471
|
const files = await walkYamlFiles(scanPath);
|
|
5774
6472
|
for (const file of files) {
|
|
5775
|
-
const content = await
|
|
6473
|
+
const content = await import_node_fs14.promises.readFile(file, "utf8");
|
|
5776
6474
|
let docs;
|
|
5777
6475
|
try {
|
|
5778
|
-
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
5779
|
-
} catch {
|
|
5780
|
-
continue;
|
|
5781
|
-
}
|
|
5782
|
-
for (const doc of docs) {
|
|
5783
|
-
if (!doc?.kind || !doc.metadata?.name) continue;
|
|
5784
|
-
if (!K8S_KINDS_WITH_HOSTNAMES.has(doc.kind)) continue;
|
|
5785
|
-
const target = k8sServiceTarget(doc, serviceIndex);
|
|
5786
|
-
if (!target) continue;
|
|
5787
|
-
addAliases(graph, target, k8sHostnames(doc.metadata.name, doc.metadata.namespace));
|
|
5788
|
-
}
|
|
5789
|
-
}
|
|
5790
|
-
}
|
|
5791
|
-
async function addServiceAliases(graph, scanPath, services) {
|
|
5792
|
-
const byName = indexServicesByName(services);
|
|
5793
|
-
await collectComposeAliases(graph, scanPath, byName);
|
|
5794
|
-
await collectDockerfileAliases(graph, services);
|
|
5795
|
-
await collectK8sAliases(graph, scanPath, byName);
|
|
5796
|
-
}
|
|
5797
|
-
|
|
5798
|
-
// src/extract/files.ts
|
|
5799
|
-
init_cjs_shims();
|
|
5800
|
-
var import_node_path15 = __toESM(require("path"), 1);
|
|
5801
|
-
async function addFiles(graph, services) {
|
|
5802
|
-
let nodesAdded = 0;
|
|
5803
|
-
let edgesAdded = 0;
|
|
5804
|
-
for (const service of services) {
|
|
5805
|
-
const filePaths = await walkSourceFiles(service.dir);
|
|
5806
|
-
for (const filePath of filePaths) {
|
|
5807
|
-
const relPath = toPosix(import_node_path15.default.relative(service.dir, filePath));
|
|
5808
|
-
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5809
|
-
graph,
|
|
5810
|
-
service.pkg.name,
|
|
5811
|
-
service.node.id,
|
|
5812
|
-
relPath
|
|
5813
|
-
);
|
|
5814
|
-
nodesAdded += n;
|
|
5815
|
-
edgesAdded += e;
|
|
5816
|
-
}
|
|
5817
|
-
}
|
|
5818
|
-
return { nodesAdded, edgesAdded };
|
|
5819
|
-
}
|
|
5820
|
-
|
|
5821
|
-
// src/extract/symbols.ts
|
|
5822
|
-
init_cjs_shims();
|
|
5823
|
-
var import_node_path16 = __toESM(require("path"), 1);
|
|
5824
|
-
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
5825
|
-
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5826
|
-
var import_tree_sitter_typescript = __toESM(require("tree-sitter-typescript"), 1);
|
|
5827
|
-
var import_types12 = require("@neat.is/types");
|
|
5828
|
-
var PARSE_CHUNK2 = 16384;
|
|
5829
|
-
var GRAMMAR_BY_EXT = {
|
|
5830
|
-
".ts": import_tree_sitter_typescript.default.typescript,
|
|
5831
|
-
".tsx": import_tree_sitter_typescript.default.tsx,
|
|
5832
|
-
".js": import_tree_sitter_javascript2.default,
|
|
5833
|
-
".jsx": import_tree_sitter_javascript2.default,
|
|
5834
|
-
".mjs": import_tree_sitter_javascript2.default,
|
|
5835
|
-
".cjs": import_tree_sitter_javascript2.default
|
|
5836
|
-
};
|
|
5837
|
-
function parseSource2(parser, source) {
|
|
5838
|
-
return parser.parse(
|
|
5839
|
-
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
5840
|
-
);
|
|
5841
|
-
}
|
|
5842
|
-
function methodName(node) {
|
|
5843
|
-
const name = node.childForFieldName("name");
|
|
5844
|
-
return name ? name.text : null;
|
|
5845
|
-
}
|
|
5846
|
-
function collectSymbolDefs(root) {
|
|
5847
|
-
const out = [];
|
|
5848
|
-
const push = (kind, qualname, node) => {
|
|
5849
|
-
out.push({
|
|
5850
|
-
kind,
|
|
5851
|
-
qualname,
|
|
5852
|
-
startLine: node.startPosition.row + 1,
|
|
5853
|
-
endLine: node.endPosition.row + 1
|
|
5854
|
-
});
|
|
5855
|
-
};
|
|
5856
|
-
const visit = (node, classCtx) => {
|
|
5857
|
-
switch (node.type) {
|
|
5858
|
-
case "function_declaration":
|
|
5859
|
-
case "generator_function_declaration": {
|
|
5860
|
-
const name = node.childForFieldName("name")?.text;
|
|
5861
|
-
if (name) push("function", name, node);
|
|
5862
|
-
break;
|
|
5863
|
-
}
|
|
5864
|
-
case "class_declaration":
|
|
5865
|
-
case "abstract_class_declaration":
|
|
5866
|
-
case "class": {
|
|
5867
|
-
const name = node.childForFieldName("name")?.text;
|
|
5868
|
-
if (name) push("class", name, node);
|
|
5869
|
-
const body = node.childForFieldName("body");
|
|
5870
|
-
if (body) {
|
|
5871
|
-
for (let i = 0; i < body.namedChildCount; i++) {
|
|
5872
|
-
const child = body.namedChild(i);
|
|
5873
|
-
if (child) visit(child, name ?? classCtx);
|
|
5874
|
-
}
|
|
5875
|
-
}
|
|
5876
|
-
return;
|
|
5877
|
-
}
|
|
5878
|
-
case "method_definition": {
|
|
5879
|
-
const name = methodName(node);
|
|
5880
|
-
if (name) {
|
|
5881
|
-
const kind = name === "constructor" ? "constructor" : "method";
|
|
5882
|
-
push(kind, classCtx ? `${classCtx}.${name}` : name, node);
|
|
5883
|
-
}
|
|
5884
|
-
break;
|
|
5885
|
-
}
|
|
5886
|
-
case "variable_declarator": {
|
|
5887
|
-
const value = node.childForFieldName("value");
|
|
5888
|
-
if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
|
|
5889
|
-
const nameNode = node.childForFieldName("name");
|
|
5890
|
-
if (nameNode && nameNode.type === "identifier") {
|
|
5891
|
-
push("function", nameNode.text, node);
|
|
5892
|
-
}
|
|
5893
|
-
}
|
|
5894
|
-
break;
|
|
5895
|
-
}
|
|
5896
|
-
}
|
|
5897
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5898
|
-
const child = node.namedChild(i);
|
|
5899
|
-
if (child) visit(child, classCtx);
|
|
5900
|
-
}
|
|
5901
|
-
};
|
|
5902
|
-
visit(root, void 0);
|
|
5903
|
-
return out;
|
|
5904
|
-
}
|
|
5905
|
-
function disambiguate(defs) {
|
|
5906
|
-
const counts = /* @__PURE__ */ new Map();
|
|
5907
|
-
for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
|
|
5908
|
-
const seen = /* @__PURE__ */ new Map();
|
|
5909
|
-
return defs.map((def) => {
|
|
5910
|
-
if ((counts.get(def.qualname) ?? 0) <= 1) return { def };
|
|
5911
|
-
const ordinal = seen.get(def.qualname) ?? 0;
|
|
5912
|
-
seen.set(def.qualname, ordinal + 1);
|
|
5913
|
-
return { def, disambiguator: ordinal };
|
|
5914
|
-
});
|
|
5915
|
-
}
|
|
5916
|
-
async function addSymbols(graph, services) {
|
|
5917
|
-
const parsers = /* @__PURE__ */ new Map();
|
|
5918
|
-
const parserForExt2 = (ext) => {
|
|
5919
|
-
const grammar = GRAMMAR_BY_EXT[ext];
|
|
5920
|
-
if (!grammar) return null;
|
|
5921
|
-
let parser = parsers.get(ext);
|
|
5922
|
-
if (!parser) {
|
|
5923
|
-
parser = new import_tree_sitter2.default();
|
|
5924
|
-
parser.setLanguage(grammar);
|
|
5925
|
-
parsers.set(ext, parser);
|
|
6476
|
+
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
6477
|
+
} catch {
|
|
6478
|
+
continue;
|
|
5926
6479
|
}
|
|
5927
|
-
|
|
5928
|
-
|
|
6480
|
+
for (const doc of docs) {
|
|
6481
|
+
if (!doc?.kind || !doc.metadata?.name) continue;
|
|
6482
|
+
if (!K8S_KINDS_WITH_HOSTNAMES.has(doc.kind)) continue;
|
|
6483
|
+
const target = k8sServiceTarget(doc, serviceIndex);
|
|
6484
|
+
if (!target) continue;
|
|
6485
|
+
addAliases(graph, target, k8sHostnames(doc.metadata.name, doc.metadata.namespace));
|
|
6486
|
+
}
|
|
6487
|
+
}
|
|
6488
|
+
}
|
|
6489
|
+
async function addServiceAliases(graph, scanPath, services) {
|
|
6490
|
+
const byName = indexServicesByName(services);
|
|
6491
|
+
await collectComposeAliases(graph, scanPath, byName);
|
|
6492
|
+
await collectDockerfileAliases(graph, services);
|
|
6493
|
+
await collectK8sAliases(graph, scanPath, byName);
|
|
6494
|
+
}
|
|
6495
|
+
|
|
6496
|
+
// src/extract/files.ts
|
|
6497
|
+
init_cjs_shims();
|
|
6498
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
6499
|
+
async function addFiles(graph, services) {
|
|
5929
6500
|
let nodesAdded = 0;
|
|
5930
6501
|
let edgesAdded = 0;
|
|
5931
6502
|
for (const service of services) {
|
|
5932
|
-
const
|
|
5933
|
-
for (const
|
|
5934
|
-
const
|
|
5935
|
-
|
|
5936
|
-
const relPath = toPosix(import_node_path16.default.relative(service.dir, file.path));
|
|
5937
|
-
let defs;
|
|
5938
|
-
try {
|
|
5939
|
-
const tree = parseSource2(parser, file.content);
|
|
5940
|
-
defs = collectSymbolDefs(tree.rootNode);
|
|
5941
|
-
} catch (err) {
|
|
5942
|
-
recordExtractionError("symbol extraction", file.path, err);
|
|
5943
|
-
continue;
|
|
5944
|
-
}
|
|
5945
|
-
if (defs.length === 0) continue;
|
|
5946
|
-
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
6503
|
+
const filePaths = await walkSourceFiles(service.dir);
|
|
6504
|
+
for (const filePath of filePaths) {
|
|
6505
|
+
const relPath = toPosix(import_node_path16.default.relative(service.dir, filePath));
|
|
6506
|
+
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5947
6507
|
graph,
|
|
5948
6508
|
service.pkg.name,
|
|
5949
6509
|
service.node.id,
|
|
5950
6510
|
relPath
|
|
5951
6511
|
);
|
|
5952
|
-
nodesAdded +=
|
|
5953
|
-
edgesAdded +=
|
|
5954
|
-
for (const { def, disambiguator } of disambiguate(defs)) {
|
|
5955
|
-
const sid = (0, import_types12.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
|
|
5956
|
-
if (!graph.hasNode(sid)) {
|
|
5957
|
-
const node = {
|
|
5958
|
-
id: sid,
|
|
5959
|
-
type: import_types12.NodeType.SymbolNode,
|
|
5960
|
-
kind: def.kind,
|
|
5961
|
-
qualname: def.qualname,
|
|
5962
|
-
span: { startLine: def.startLine, endLine: def.endLine },
|
|
5963
|
-
service: service.pkg.name,
|
|
5964
|
-
relPath,
|
|
5965
|
-
discoveredVia: "static"
|
|
5966
|
-
};
|
|
5967
|
-
graph.addNode(sid, node);
|
|
5968
|
-
nodesAdded++;
|
|
5969
|
-
}
|
|
5970
|
-
const containsId = (0, import_types12.extractedEdgeId)(fileNodeId, sid, import_types12.EdgeType.CONTAINS);
|
|
5971
|
-
if (!graph.hasEdge(containsId)) {
|
|
5972
|
-
const edge = {
|
|
5973
|
-
id: containsId,
|
|
5974
|
-
source: fileNodeId,
|
|
5975
|
-
target: sid,
|
|
5976
|
-
type: import_types12.EdgeType.CONTAINS,
|
|
5977
|
-
provenance: import_types12.Provenance.EXTRACTED,
|
|
5978
|
-
confidence: (0, import_types12.confidenceForExtracted)("structural"),
|
|
5979
|
-
evidence: {
|
|
5980
|
-
file: relPath,
|
|
5981
|
-
line: def.startLine,
|
|
5982
|
-
snippet: snippet(file.content, def.startLine)
|
|
5983
|
-
}
|
|
5984
|
-
};
|
|
5985
|
-
graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
|
|
5986
|
-
edgesAdded++;
|
|
5987
|
-
}
|
|
5988
|
-
}
|
|
6512
|
+
nodesAdded += n;
|
|
6513
|
+
edgesAdded += e;
|
|
5989
6514
|
}
|
|
5990
6515
|
}
|
|
5991
6516
|
return { nodesAdded, edgesAdded };
|
|
5992
6517
|
}
|
|
5993
6518
|
|
|
5994
|
-
// src/extract/
|
|
5995
|
-
init_cjs_shims();
|
|
5996
|
-
var import_node_path18 = __toESM(require("path"), 1);
|
|
5997
|
-
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
5998
|
-
var import_types14 = require("@neat.is/types");
|
|
5999
|
-
|
|
6000
|
-
// src/extract/imports.ts
|
|
6519
|
+
// src/extract/symbols.ts
|
|
6001
6520
|
init_cjs_shims();
|
|
6002
6521
|
var import_node_path17 = __toESM(require("path"), 1);
|
|
6003
|
-
var import_node_fs14 = require("fs");
|
|
6004
6522
|
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
6005
6523
|
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
6006
|
-
var
|
|
6007
|
-
var import_tree_sitter_go2 = __toESM(require("tree-sitter-go"), 1);
|
|
6524
|
+
var import_tree_sitter_typescript = __toESM(require("tree-sitter-typescript"), 1);
|
|
6008
6525
|
var import_types13 = require("@neat.is/types");
|
|
6009
6526
|
var PARSE_CHUNK3 = 16384;
|
|
6527
|
+
var GRAMMAR_BY_EXT = {
|
|
6528
|
+
".ts": import_tree_sitter_typescript.default.typescript,
|
|
6529
|
+
".tsx": import_tree_sitter_typescript.default.tsx,
|
|
6530
|
+
".js": import_tree_sitter_javascript3.default,
|
|
6531
|
+
".jsx": import_tree_sitter_javascript3.default,
|
|
6532
|
+
".mjs": import_tree_sitter_javascript3.default,
|
|
6533
|
+
".cjs": import_tree_sitter_javascript3.default
|
|
6534
|
+
};
|
|
6010
6535
|
function parseSource3(parser, source) {
|
|
6011
6536
|
return parser.parse(
|
|
6012
6537
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
|
|
6013
|
-
);
|
|
6014
|
-
}
|
|
6015
|
-
function makeJsParser2() {
|
|
6016
|
-
const p = new import_tree_sitter3.default();
|
|
6017
|
-
p.setLanguage(import_tree_sitter_javascript3.default);
|
|
6018
|
-
return p;
|
|
6019
|
-
}
|
|
6020
|
-
function makePyParser2() {
|
|
6021
|
-
const p = new import_tree_sitter3.default();
|
|
6022
|
-
p.setLanguage(import_tree_sitter_python2.default);
|
|
6023
|
-
return p;
|
|
6024
|
-
}
|
|
6025
|
-
function makeGoParser2() {
|
|
6026
|
-
const p = new import_tree_sitter3.default();
|
|
6027
|
-
p.setLanguage(import_tree_sitter_go2.default);
|
|
6028
|
-
return p;
|
|
6029
|
-
}
|
|
6030
|
-
function stringLiteralText(node) {
|
|
6031
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
6032
|
-
const child = node.child(i);
|
|
6033
|
-
if (child?.type === "string_fragment") return child.text;
|
|
6034
|
-
}
|
|
6035
|
-
const raw = node.text;
|
|
6036
|
-
if (raw.length >= 2) return raw.slice(1, -1);
|
|
6037
|
-
return raw.length === 0 ? null : "";
|
|
6038
|
-
}
|
|
6039
|
-
function clipSnippet(text) {
|
|
6040
|
-
const oneLine = text.split("\n")[0] ?? text;
|
|
6041
|
-
return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
|
|
6042
|
-
}
|
|
6043
|
-
function collectGoImports(node, out) {
|
|
6044
|
-
if (node.type === "import_spec") {
|
|
6045
|
-
const pathNode = node.childForFieldName("path");
|
|
6046
|
-
if (pathNode) {
|
|
6047
|
-
const specifier = pathNode.text.replace(/^`|`$/g, "").replace(/^"|"$/g, "");
|
|
6048
|
-
if (specifier) out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
6049
|
-
}
|
|
6050
|
-
return;
|
|
6051
|
-
}
|
|
6052
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6053
|
-
const child = node.namedChild(i);
|
|
6054
|
-
if (child) collectGoImports(child, out);
|
|
6055
|
-
}
|
|
6056
|
-
}
|
|
6057
|
-
function collectJsImports(node, out) {
|
|
6058
|
-
if (node.type === "import_statement") {
|
|
6059
|
-
const source = node.childForFieldName("source");
|
|
6060
|
-
if (source) {
|
|
6061
|
-
const specifier = stringLiteralText(source);
|
|
6062
|
-
if (specifier) {
|
|
6063
|
-
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
6064
|
-
}
|
|
6065
|
-
}
|
|
6066
|
-
return;
|
|
6067
|
-
}
|
|
6068
|
-
if (node.type === "call_expression") {
|
|
6069
|
-
const fn = node.childForFieldName("function");
|
|
6070
|
-
if (fn?.type === "identifier" && fn.text === "require") {
|
|
6071
|
-
const args = node.childForFieldName("arguments");
|
|
6072
|
-
const firstArg = args?.namedChild(0);
|
|
6073
|
-
if (firstArg?.type === "string") {
|
|
6074
|
-
const specifier = stringLiteralText(firstArg);
|
|
6075
|
-
if (specifier) {
|
|
6076
|
-
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
6077
|
-
}
|
|
6078
|
-
}
|
|
6079
|
-
}
|
|
6080
|
-
}
|
|
6081
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6082
|
-
const child = node.namedChild(i);
|
|
6083
|
-
if (child) collectJsImports(child, out);
|
|
6084
|
-
}
|
|
6085
|
-
}
|
|
6086
|
-
function collectImportedNames(node, out) {
|
|
6087
|
-
if (node.type === "aliased_import") {
|
|
6088
|
-
const nameNode = node.childForFieldName("name");
|
|
6089
|
-
if (nameNode) out.push(nameNode.text);
|
|
6090
|
-
return;
|
|
6091
|
-
}
|
|
6092
|
-
if (node.type === "dotted_name") {
|
|
6093
|
-
out.push(node.text);
|
|
6094
|
-
return;
|
|
6095
|
-
}
|
|
6096
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6097
|
-
const child = node.namedChild(i);
|
|
6098
|
-
if (child) collectImportedNames(child, out);
|
|
6099
|
-
}
|
|
6100
|
-
}
|
|
6101
|
-
function collectPyImports(node, out) {
|
|
6102
|
-
if (node.type === "import_from_statement") {
|
|
6103
|
-
let level = 0;
|
|
6104
|
-
let modulePath = "";
|
|
6105
|
-
const names = [];
|
|
6106
|
-
let pastFrom = false;
|
|
6107
|
-
let pastImport = false;
|
|
6108
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
6109
|
-
const child = node.child(i);
|
|
6110
|
-
if (!child) continue;
|
|
6111
|
-
if (!pastFrom) {
|
|
6112
|
-
if (child.type === "from") pastFrom = true;
|
|
6113
|
-
continue;
|
|
6114
|
-
}
|
|
6115
|
-
if (!pastImport) {
|
|
6116
|
-
if (child.type === "import") {
|
|
6117
|
-
pastImport = true;
|
|
6118
|
-
continue;
|
|
6119
|
-
}
|
|
6120
|
-
if (child.type === "relative_import") {
|
|
6121
|
-
for (let j = 0; j < child.childCount; j++) {
|
|
6122
|
-
const rc = child.child(j);
|
|
6123
|
-
if (!rc) continue;
|
|
6124
|
-
if (rc.type === "import_prefix") {
|
|
6125
|
-
for (let k = 0; k < rc.childCount; k++) {
|
|
6126
|
-
if (rc.child(k)?.type === ".") level++;
|
|
6127
|
-
}
|
|
6128
|
-
} else if (rc.type === "dotted_name") modulePath = rc.text;
|
|
6129
|
-
}
|
|
6130
|
-
} else if (child.type === "dotted_name") {
|
|
6131
|
-
modulePath = child.text;
|
|
6132
|
-
}
|
|
6133
|
-
continue;
|
|
6134
|
-
}
|
|
6135
|
-
collectImportedNames(child, names);
|
|
6136
|
-
}
|
|
6137
|
-
if (level > 0 || modulePath) {
|
|
6138
|
-
out.push({ modulePath, level, names, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
6139
|
-
}
|
|
6140
|
-
}
|
|
6141
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6142
|
-
const child = node.namedChild(i);
|
|
6143
|
-
if (child) collectPyImports(child, out);
|
|
6144
|
-
}
|
|
6145
|
-
}
|
|
6146
|
-
async function fileExists2(p) {
|
|
6147
|
-
try {
|
|
6148
|
-
await import_node_fs14.promises.access(p);
|
|
6149
|
-
return true;
|
|
6150
|
-
} catch {
|
|
6151
|
-
return false;
|
|
6152
|
-
}
|
|
6153
|
-
}
|
|
6154
|
-
function isWithinServiceDir(candidate, serviceDir) {
|
|
6155
|
-
const rel = import_node_path17.default.relative(serviceDir, candidate);
|
|
6156
|
-
return rel !== "" && !rel.startsWith("..") && !import_node_path17.default.isAbsolute(rel);
|
|
6157
|
-
}
|
|
6158
|
-
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
6159
|
-
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
6160
|
-
async function firstExistingCandidate(base, serviceDir) {
|
|
6161
|
-
for (const ext of JS_EXTENSIONS) {
|
|
6162
|
-
const candidate = base + ext;
|
|
6163
|
-
if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
|
|
6164
|
-
return toPosix(import_node_path17.default.relative(serviceDir, candidate));
|
|
6165
|
-
}
|
|
6166
|
-
}
|
|
6167
|
-
for (const indexFile of JS_INDEX_FILES) {
|
|
6168
|
-
const candidate = import_node_path17.default.join(base, indexFile);
|
|
6169
|
-
if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
|
|
6170
|
-
return toPosix(import_node_path17.default.relative(serviceDir, candidate));
|
|
6171
|
-
}
|
|
6172
|
-
}
|
|
6173
|
-
return null;
|
|
6174
|
-
}
|
|
6175
|
-
async function loadTsPathConfig(serviceDir) {
|
|
6176
|
-
const tsconfigPath = import_node_path17.default.join(serviceDir, "tsconfig.json");
|
|
6177
|
-
let raw;
|
|
6178
|
-
try {
|
|
6179
|
-
raw = await import_node_fs14.promises.readFile(tsconfigPath, "utf8");
|
|
6180
|
-
} catch {
|
|
6181
|
-
return null;
|
|
6182
|
-
}
|
|
6183
|
-
try {
|
|
6184
|
-
const parsed = JSON.parse(raw);
|
|
6185
|
-
const paths = parsed.compilerOptions?.paths;
|
|
6186
|
-
if (!paths || Object.keys(paths).length === 0) return null;
|
|
6187
|
-
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
6188
|
-
return { paths, baseDir: baseUrl ? import_node_path17.default.resolve(serviceDir, baseUrl) : serviceDir };
|
|
6189
|
-
} catch (err) {
|
|
6190
|
-
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
6191
|
-
return null;
|
|
6192
|
-
}
|
|
6538
|
+
);
|
|
6193
6539
|
}
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
if (pattern === specifier) {
|
|
6198
|
-
suffix = "";
|
|
6199
|
-
} else if (pattern.endsWith("/*")) {
|
|
6200
|
-
const prefix = pattern.slice(0, -1);
|
|
6201
|
-
if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
|
|
6202
|
-
}
|
|
6203
|
-
if (suffix === null) continue;
|
|
6204
|
-
for (const target of targets) {
|
|
6205
|
-
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
6206
|
-
const resolvedBase = import_node_path17.default.resolve(config.baseDir, targetBase, suffix);
|
|
6207
|
-
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
6208
|
-
if (hit) return hit;
|
|
6209
|
-
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists2(resolvedBase)) {
|
|
6210
|
-
return toPosix(import_node_path17.default.relative(serviceDir, resolvedBase));
|
|
6211
|
-
}
|
|
6212
|
-
}
|
|
6213
|
-
}
|
|
6214
|
-
return null;
|
|
6540
|
+
function methodName(node) {
|
|
6541
|
+
const name = node.childForFieldName("name");
|
|
6542
|
+
return name ? name.text : null;
|
|
6215
6543
|
}
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6544
|
+
function collectSymbolDefs(root) {
|
|
6545
|
+
const out = [];
|
|
6546
|
+
const push = (kind, qualname, node) => {
|
|
6547
|
+
out.push({
|
|
6548
|
+
kind,
|
|
6549
|
+
qualname,
|
|
6550
|
+
startLine: node.startPosition.row + 1,
|
|
6551
|
+
endLine: node.endPosition.row + 1
|
|
6552
|
+
});
|
|
6553
|
+
};
|
|
6554
|
+
const visit = (node, classCtx) => {
|
|
6555
|
+
switch (node.type) {
|
|
6556
|
+
case "function_declaration":
|
|
6557
|
+
case "generator_function_declaration": {
|
|
6558
|
+
const name = node.childForFieldName("name")?.text;
|
|
6559
|
+
if (name) push("function", name, node);
|
|
6560
|
+
break;
|
|
6561
|
+
}
|
|
6562
|
+
case "class_declaration":
|
|
6563
|
+
case "abstract_class_declaration":
|
|
6564
|
+
case "class": {
|
|
6565
|
+
const name = node.childForFieldName("name")?.text;
|
|
6566
|
+
if (name) push("class", name, node);
|
|
6567
|
+
const body = node.childForFieldName("body");
|
|
6568
|
+
if (body) {
|
|
6569
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
6570
|
+
const child = body.namedChild(i);
|
|
6571
|
+
if (child) visit(child, name ?? classCtx);
|
|
6572
|
+
}
|
|
6227
6573
|
}
|
|
6574
|
+
return;
|
|
6228
6575
|
}
|
|
6229
|
-
|
|
6230
|
-
|
|
6576
|
+
case "method_definition": {
|
|
6577
|
+
const name = methodName(node);
|
|
6578
|
+
if (name) {
|
|
6579
|
+
const kind = name === "constructor" ? "constructor" : "method";
|
|
6580
|
+
push(kind, classCtx ? `${classCtx}.${name}` : name, node);
|
|
6581
|
+
}
|
|
6582
|
+
break;
|
|
6231
6583
|
}
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
}
|
|
6239
|
-
|
|
6240
|
-
let baseDir;
|
|
6241
|
-
if (imp.level > 0) {
|
|
6242
|
-
baseDir = import_node_path17.default.dirname(importerPath);
|
|
6243
|
-
for (let i = 1; i < imp.level; i++) baseDir = import_node_path17.default.dirname(baseDir);
|
|
6244
|
-
} else {
|
|
6245
|
-
baseDir = serviceDir;
|
|
6246
|
-
}
|
|
6247
|
-
const moduleBase = imp.modulePath ? import_node_path17.default.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
|
|
6248
|
-
const resolved = /* @__PURE__ */ new Set();
|
|
6249
|
-
let needModuleFile = imp.names.length === 0;
|
|
6250
|
-
for (const name of imp.names) {
|
|
6251
|
-
const submoduleFile = import_node_path17.default.join(moduleBase, `${name}.py`);
|
|
6252
|
-
const subpackageInit = import_node_path17.default.join(moduleBase, name, "__init__.py");
|
|
6253
|
-
if (isWithinServiceDir(submoduleFile, serviceDir) && await fileExists2(submoduleFile)) {
|
|
6254
|
-
resolved.add(toPosix(import_node_path17.default.relative(serviceDir, submoduleFile)));
|
|
6255
|
-
} else if (isWithinServiceDir(subpackageInit, serviceDir) && await fileExists2(subpackageInit)) {
|
|
6256
|
-
resolved.add(toPosix(import_node_path17.default.relative(serviceDir, subpackageInit)));
|
|
6257
|
-
} else {
|
|
6258
|
-
needModuleFile = true;
|
|
6259
|
-
}
|
|
6260
|
-
}
|
|
6261
|
-
if (needModuleFile) {
|
|
6262
|
-
const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, import_node_path17.default.join(moduleBase, "__init__.py")] : [import_node_path17.default.join(moduleBase, "__init__.py")];
|
|
6263
|
-
for (const candidate of moduleFileCandidates) {
|
|
6264
|
-
if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
|
|
6265
|
-
resolved.add(toPosix(import_node_path17.default.relative(serviceDir, candidate)));
|
|
6584
|
+
case "variable_declarator": {
|
|
6585
|
+
const value = node.childForFieldName("value");
|
|
6586
|
+
if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
|
|
6587
|
+
const nameNode = node.childForFieldName("name");
|
|
6588
|
+
if (nameNode && nameNode.type === "identifier") {
|
|
6589
|
+
push("function", nameNode.text, node);
|
|
6590
|
+
}
|
|
6591
|
+
}
|
|
6266
6592
|
break;
|
|
6267
6593
|
}
|
|
6268
6594
|
}
|
|
6269
|
-
|
|
6270
|
-
|
|
6595
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6596
|
+
const child = node.namedChild(i);
|
|
6597
|
+
if (child) visit(child, classCtx);
|
|
6598
|
+
}
|
|
6599
|
+
};
|
|
6600
|
+
visit(root, void 0);
|
|
6601
|
+
return out;
|
|
6271
6602
|
}
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
const
|
|
6275
|
-
const
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6603
|
+
function disambiguate(defs) {
|
|
6604
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6605
|
+
for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
|
|
6606
|
+
const seen = /* @__PURE__ */ new Map();
|
|
6607
|
+
return defs.map((def) => {
|
|
6608
|
+
if ((counts.get(def.qualname) ?? 0) <= 1) return { def };
|
|
6609
|
+
const ordinal = seen.get(def.qualname) ?? 0;
|
|
6610
|
+
seen.set(def.qualname, ordinal + 1);
|
|
6611
|
+
return { def, disambiguator: ordinal };
|
|
6612
|
+
});
|
|
6280
6613
|
}
|
|
6281
|
-
function
|
|
6282
|
-
const
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
evidence: { file: importerRelPath, line, snippet: snippet2 }
|
|
6614
|
+
async function addSymbols(graph, services) {
|
|
6615
|
+
const parsers = /* @__PURE__ */ new Map();
|
|
6616
|
+
const parserForExt2 = (ext) => {
|
|
6617
|
+
const grammar = GRAMMAR_BY_EXT[ext];
|
|
6618
|
+
if (!grammar) return null;
|
|
6619
|
+
let parser = parsers.get(ext);
|
|
6620
|
+
if (!parser) {
|
|
6621
|
+
parser = new import_tree_sitter3.default();
|
|
6622
|
+
parser.setLanguage(grammar);
|
|
6623
|
+
parsers.set(ext, parser);
|
|
6624
|
+
}
|
|
6625
|
+
return parser;
|
|
6294
6626
|
};
|
|
6295
|
-
|
|
6296
|
-
return 1;
|
|
6297
|
-
}
|
|
6298
|
-
async function addImports(graph, services) {
|
|
6299
|
-
const jsParser = makeJsParser2();
|
|
6300
|
-
const pyParser = makePyParser2();
|
|
6301
|
-
const goParser = makeGoParser2();
|
|
6627
|
+
let nodesAdded = 0;
|
|
6302
6628
|
let edgesAdded = 0;
|
|
6303
6629
|
for (const service of services) {
|
|
6304
|
-
const tsPaths = await loadTsPathConfig(service.dir);
|
|
6305
6630
|
const files = await loadSourceFiles(service.dir);
|
|
6306
6631
|
for (const file of files) {
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
const
|
|
6310
|
-
|
|
6311
|
-
const isGo = import_node_path17.default.extname(file.path) === ".go";
|
|
6312
|
-
if (isGo) {
|
|
6313
|
-
let goImports = [];
|
|
6314
|
-
try {
|
|
6315
|
-
const tree = parseSource3(goParser, file.content);
|
|
6316
|
-
collectGoImports(tree.rootNode, goImports);
|
|
6317
|
-
} catch (err) {
|
|
6318
|
-
recordExtractionError("import extraction", file.path, err);
|
|
6319
|
-
continue;
|
|
6320
|
-
}
|
|
6321
|
-
const goMod = await import_node_fs14.promises.readFile(import_node_path17.default.join(service.dir, "go.mod"), "utf8").catch(() => "");
|
|
6322
|
-
const modulePath = goMod.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
|
|
6323
|
-
if (!modulePath) continue;
|
|
6324
|
-
for (const imp of goImports) {
|
|
6325
|
-
const resolved = await resolveGoImport(imp.specifier, modulePath, service.dir);
|
|
6326
|
-
if (!resolved) continue;
|
|
6327
|
-
edgesAdded += emitImportEdge(graph, service.pkg.name, importerFileId, relFile, resolved, imp.line, imp.snippet);
|
|
6328
|
-
}
|
|
6329
|
-
continue;
|
|
6330
|
-
}
|
|
6331
|
-
if (isPython) {
|
|
6332
|
-
let pyImports = [];
|
|
6333
|
-
try {
|
|
6334
|
-
const tree = parseSource3(pyParser, file.content);
|
|
6335
|
-
collectPyImports(tree.rootNode, pyImports);
|
|
6336
|
-
} catch (err) {
|
|
6337
|
-
recordExtractionError("import extraction", file.path, err);
|
|
6338
|
-
continue;
|
|
6339
|
-
}
|
|
6340
|
-
for (const imp of pyImports) {
|
|
6341
|
-
const resolvedPaths = await resolvePyImport(imp, file.path, service.dir);
|
|
6342
|
-
for (const resolved of resolvedPaths) {
|
|
6343
|
-
edgesAdded += emitImportEdge(
|
|
6344
|
-
graph,
|
|
6345
|
-
service.pkg.name,
|
|
6346
|
-
importerFileId,
|
|
6347
|
-
relFile,
|
|
6348
|
-
resolved,
|
|
6349
|
-
imp.line,
|
|
6350
|
-
imp.snippet
|
|
6351
|
-
);
|
|
6352
|
-
}
|
|
6353
|
-
}
|
|
6354
|
-
continue;
|
|
6355
|
-
}
|
|
6356
|
-
let jsImports = [];
|
|
6632
|
+
const parser = parserForExt2(import_node_path17.default.extname(file.path));
|
|
6633
|
+
if (!parser) continue;
|
|
6634
|
+
const relPath = toPosix(import_node_path17.default.relative(service.dir, file.path));
|
|
6635
|
+
let defs;
|
|
6357
6636
|
try {
|
|
6358
|
-
const tree = parseSource3(
|
|
6359
|
-
|
|
6637
|
+
const tree = parseSource3(parser, file.content);
|
|
6638
|
+
defs = collectSymbolDefs(tree.rootNode);
|
|
6360
6639
|
} catch (err) {
|
|
6361
|
-
recordExtractionError("
|
|
6640
|
+
recordExtractionError("symbol extraction", file.path, err);
|
|
6362
6641
|
continue;
|
|
6363
6642
|
}
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
)
|
|
6643
|
+
if (defs.length === 0) continue;
|
|
6644
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
6645
|
+
graph,
|
|
6646
|
+
service.pkg.name,
|
|
6647
|
+
service.node.id,
|
|
6648
|
+
relPath
|
|
6649
|
+
);
|
|
6650
|
+
nodesAdded += fn;
|
|
6651
|
+
edgesAdded += fe;
|
|
6652
|
+
for (const { def, disambiguator } of disambiguate(defs)) {
|
|
6653
|
+
const sid = (0, import_types13.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
|
|
6654
|
+
if (!graph.hasNode(sid)) {
|
|
6655
|
+
const node = {
|
|
6656
|
+
id: sid,
|
|
6657
|
+
type: import_types13.NodeType.SymbolNode,
|
|
6658
|
+
kind: def.kind,
|
|
6659
|
+
qualname: def.qualname,
|
|
6660
|
+
span: { startLine: def.startLine, endLine: def.endLine },
|
|
6661
|
+
service: service.pkg.name,
|
|
6662
|
+
relPath,
|
|
6663
|
+
discoveredVia: "static"
|
|
6664
|
+
};
|
|
6665
|
+
graph.addNode(sid, node);
|
|
6666
|
+
nodesAdded++;
|
|
6667
|
+
}
|
|
6668
|
+
const containsId = (0, import_types13.extractedEdgeId)(fileNodeId, sid, import_types13.EdgeType.CONTAINS);
|
|
6669
|
+
if (!graph.hasEdge(containsId)) {
|
|
6670
|
+
const edge = {
|
|
6671
|
+
id: containsId,
|
|
6672
|
+
source: fileNodeId,
|
|
6673
|
+
target: sid,
|
|
6674
|
+
type: import_types13.EdgeType.CONTAINS,
|
|
6675
|
+
provenance: import_types13.Provenance.EXTRACTED,
|
|
6676
|
+
confidence: (0, import_types13.confidenceForExtracted)("structural"),
|
|
6677
|
+
evidence: {
|
|
6678
|
+
file: relPath,
|
|
6679
|
+
line: def.startLine,
|
|
6680
|
+
snippet: snippet(file.content, def.startLine)
|
|
6681
|
+
}
|
|
6682
|
+
};
|
|
6683
|
+
graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
|
|
6684
|
+
edgesAdded++;
|
|
6685
|
+
}
|
|
6376
6686
|
}
|
|
6377
6687
|
}
|
|
6378
6688
|
}
|
|
6379
|
-
return { nodesAdded
|
|
6689
|
+
return { nodesAdded, edgesAdded };
|
|
6380
6690
|
}
|
|
6381
6691
|
|
|
6382
6692
|
// src/extract/symbol-edges.ts
|
|
6693
|
+
init_cjs_shims();
|
|
6694
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
6695
|
+
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
6696
|
+
var import_types14 = require("@neat.is/types");
|
|
6383
6697
|
function extendsInfo(classHeritage) {
|
|
6384
6698
|
for (let i = 0; i < classHeritage.namedChildCount; i++) {
|
|
6385
6699
|
const child = classHeritage.namedChild(i);
|
|
@@ -6490,7 +6804,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
6490
6804
|
const fileDir = import_node_path18.default.dirname(file.path);
|
|
6491
6805
|
let root;
|
|
6492
6806
|
try {
|
|
6493
|
-
root =
|
|
6807
|
+
root = parseSource3(parser, file.content).rootNode;
|
|
6494
6808
|
} catch (err) {
|
|
6495
6809
|
recordExtractionError("symbol edge extraction", file.path, err);
|
|
6496
6810
|
continue;
|
|
@@ -8942,7 +9256,7 @@ function columnsFromObject(obj) {
|
|
|
8942
9256
|
}
|
|
8943
9257
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
8944
9258
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
8945
|
-
const tree =
|
|
9259
|
+
const tree = parseSource3(parserForExt(import_node_path39.default.extname(file.path)), file.content);
|
|
8946
9260
|
const out = [];
|
|
8947
9261
|
const seen = /* @__PURE__ */ new Set();
|
|
8948
9262
|
const walk6 = (node) => {
|