@neat.is/core 0.9.1-dev.20260819 → 0.9.2-dev.20260821
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-FCO5Z3RW.js → chunk-ERE47MCR.js} +2 -2
- package/dist/{chunk-UUYCTH2E.js → chunk-GDGUY4T6.js} +34 -2
- package/dist/chunk-GDGUY4T6.js.map +1 -0
- package/dist/{chunk-ICO37D3H.js → chunk-L4SZIIER.js} +3 -3
- package/dist/{chunk-JUVJBH23.js → chunk-RQQUI3NQ.js} +1073 -378
- package/dist/chunk-RQQUI3NQ.js.map +1 -0
- package/dist/cli.cjs +1640 -911
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/index.cjs +1303 -574
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +1319 -590
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-CO47JRIN.js → otel-grpc-Y5K4WGWO.js} +3 -3
- package/dist/server.cjs +1206 -477
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-JUVJBH23.js.map +0 -1
- package/dist/chunk-UUYCTH2E.js.map +0 -1
- /package/dist/{chunk-FCO5Z3RW.js.map → chunk-ERE47MCR.js.map} +0 -0
- /package/dist/{chunk-ICO37D3H.js.map → chunk-L4SZIIER.js.map} +0 -0
- /package/dist/{otel-grpc-CO47JRIN.js.map → otel-grpc-Y5K4WGWO.js.map} +0 -0
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
mountBearerAuth,
|
|
4
4
|
readAuthEnv,
|
|
5
5
|
tableFromSqlStatement
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GDGUY4T6.js";
|
|
7
7
|
|
|
8
8
|
// src/graph.ts
|
|
9
9
|
import GraphDefault from "graphology";
|
|
@@ -947,6 +947,7 @@ import Python2 from "tree-sitter-python";
|
|
|
947
947
|
import Go2 from "tree-sitter-go";
|
|
948
948
|
import Ruby from "tree-sitter-ruby";
|
|
949
949
|
import Php from "tree-sitter-php";
|
|
950
|
+
import Rust from "tree-sitter-rust";
|
|
950
951
|
import {
|
|
951
952
|
EdgeType as EdgeType4,
|
|
952
953
|
NodeType as NodeType3,
|
|
@@ -1250,7 +1251,7 @@ function buildServiceHostIndex(services) {
|
|
|
1250
1251
|
async function walkSourceFiles(dir, excludeDirs = []) {
|
|
1251
1252
|
const excluded = new Set(excludeDirs.map((d) => path5.resolve(d)));
|
|
1252
1253
|
const out = [];
|
|
1253
|
-
async function
|
|
1254
|
+
async function walk10(current) {
|
|
1254
1255
|
const entries = await fs5.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
1255
1256
|
for (const entry of entries) {
|
|
1256
1257
|
const full = path5.join(current, entry.name);
|
|
@@ -1258,7 +1259,7 @@ async function walkSourceFiles(dir, excludeDirs = []) {
|
|
|
1258
1259
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1259
1260
|
if (excluded.has(path5.resolve(full))) continue;
|
|
1260
1261
|
if (await isPythonVenvDir(full)) continue;
|
|
1261
|
-
await
|
|
1262
|
+
await walk10(full);
|
|
1262
1263
|
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path5.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
|
|
1263
1264
|
// would attribute our instrumentation imports to the user's service.
|
|
1264
1265
|
!isNeatAuthoredSourceFile(entry.name)) {
|
|
@@ -1266,7 +1267,7 @@ async function walkSourceFiles(dir, excludeDirs = []) {
|
|
|
1266
1267
|
}
|
|
1267
1268
|
}
|
|
1268
1269
|
}
|
|
1269
|
-
await
|
|
1270
|
+
await walk10(dir);
|
|
1270
1271
|
return out;
|
|
1271
1272
|
}
|
|
1272
1273
|
async function loadSourceFiles(dir, excludeDirs = []) {
|
|
@@ -1791,6 +1792,11 @@ function makePhpParser() {
|
|
|
1791
1792
|
p.setLanguage(Php.php_only);
|
|
1792
1793
|
return p;
|
|
1793
1794
|
}
|
|
1795
|
+
function makeRustParser() {
|
|
1796
|
+
const p = new Parser2();
|
|
1797
|
+
p.setLanguage(Rust);
|
|
1798
|
+
return p;
|
|
1799
|
+
}
|
|
1794
1800
|
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
1795
1801
|
"get",
|
|
1796
1802
|
"post",
|
|
@@ -1862,8 +1868,8 @@ function chiRoutesFromSource(source, parser) {
|
|
|
1862
1868
|
chiWalk(tree.rootNode, "", out);
|
|
1863
1869
|
return out;
|
|
1864
1870
|
}
|
|
1865
|
-
function stripChiRegex(
|
|
1866
|
-
return
|
|
1871
|
+
function stripChiRegex(path72) {
|
|
1872
|
+
return path72.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
|
|
1867
1873
|
}
|
|
1868
1874
|
function chiWalk(node, prefix, out) {
|
|
1869
1875
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -2535,9 +2541,9 @@ function rubyRocketRoute(args) {
|
|
|
2535
2541
|
if (!pair || pair.type !== "pair") continue;
|
|
2536
2542
|
const k = pair.childForFieldName("key");
|
|
2537
2543
|
if (k?.type !== "string") continue;
|
|
2538
|
-
const
|
|
2539
|
-
if (
|
|
2540
|
-
return { path:
|
|
2544
|
+
const path72 = rubyLiteral(k);
|
|
2545
|
+
if (path72 === null) continue;
|
|
2546
|
+
return { path: path72, target: rubyLiteral(pair.childForFieldName("value")) };
|
|
2541
2547
|
}
|
|
2542
2548
|
return null;
|
|
2543
2549
|
}
|
|
@@ -2707,6 +2713,48 @@ function railsRoutesFromSource(source, parser) {
|
|
|
2707
2713
|
});
|
|
2708
2714
|
return out;
|
|
2709
2715
|
}
|
|
2716
|
+
var SINATRA_VERBS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head"]);
|
|
2717
|
+
function sinatraRoutesFromSource(source, parser) {
|
|
2718
|
+
const tree = parseSource2(parser, source);
|
|
2719
|
+
if (!fileReferencesSinatra(tree.rootNode)) return [];
|
|
2720
|
+
const out = [];
|
|
2721
|
+
walk(tree.rootNode, (node) => {
|
|
2722
|
+
if (node.type !== "call") return;
|
|
2723
|
+
if (node.childForFieldName("receiver")) return;
|
|
2724
|
+
const method = node.childForFieldName("method")?.text;
|
|
2725
|
+
if (!method || !SINATRA_VERBS.has(method)) return;
|
|
2726
|
+
if (!node.childForFieldName("block")) return;
|
|
2727
|
+
const first = node.childForFieldName("arguments")?.namedChild(0);
|
|
2728
|
+
if (first?.type !== "string") return;
|
|
2729
|
+
const p = rubyLiteral(first);
|
|
2730
|
+
if (p === null || !p.startsWith("/")) return;
|
|
2731
|
+
out.push({
|
|
2732
|
+
method: method.toUpperCase(),
|
|
2733
|
+
pathTemplate: canonicalizeTemplate(p),
|
|
2734
|
+
line: node.startPosition.row + 1,
|
|
2735
|
+
framework: "sinatra"
|
|
2736
|
+
});
|
|
2737
|
+
});
|
|
2738
|
+
return out;
|
|
2739
|
+
}
|
|
2740
|
+
function fileReferencesSinatra(root) {
|
|
2741
|
+
let found = false;
|
|
2742
|
+
walk(root, (node) => {
|
|
2743
|
+
if (found) return;
|
|
2744
|
+
if (node.type === "call") {
|
|
2745
|
+
const m = node.childForFieldName("method")?.text;
|
|
2746
|
+
if (m === "require" || m === "require_relative") {
|
|
2747
|
+
const s = rubyLiteral(node.childForFieldName("arguments")?.namedChild(0));
|
|
2748
|
+
if (s !== null && /^sinatra\b/.test(s)) found = true;
|
|
2749
|
+
}
|
|
2750
|
+
return;
|
|
2751
|
+
}
|
|
2752
|
+
if (node.type === "constant" && node.text === "Sinatra" || node.type === "scope_resolution" && node.text.startsWith("Sinatra")) {
|
|
2753
|
+
found = true;
|
|
2754
|
+
}
|
|
2755
|
+
});
|
|
2756
|
+
return found;
|
|
2757
|
+
}
|
|
2710
2758
|
var LARAVEL_VERBS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options"]);
|
|
2711
2759
|
var LARAVEL_RESOURCE_ROWS = [
|
|
2712
2760
|
{ action: "index", methods: ["GET"], suffix: "" },
|
|
@@ -2882,6 +2930,223 @@ function laravelRoutesFromSource(source, parser, basePrefix = "") {
|
|
|
2882
2930
|
}
|
|
2883
2931
|
return out;
|
|
2884
2932
|
}
|
|
2933
|
+
var SLIM_VERBS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head"]);
|
|
2934
|
+
function isSlimAppCtor(node) {
|
|
2935
|
+
if (!node) return false;
|
|
2936
|
+
if (node.type === "scoped_call_expression") {
|
|
2937
|
+
const scope = node.childForFieldName("scope")?.text ?? "";
|
|
2938
|
+
const name = node.childForFieldName("name")?.text;
|
|
2939
|
+
return name === "create" && (scope === "AppFactory" || scope.endsWith("\\AppFactory"));
|
|
2940
|
+
}
|
|
2941
|
+
if (node.type === "object_creation_expression") {
|
|
2942
|
+
const cls = node.namedChild(0)?.text ?? "";
|
|
2943
|
+
return cls === "App" || cls.endsWith("\\App") || cls.includes("Slim");
|
|
2944
|
+
}
|
|
2945
|
+
return false;
|
|
2946
|
+
}
|
|
2947
|
+
function isSlimAppType(node) {
|
|
2948
|
+
if (!node || node.type !== "named_type") return false;
|
|
2949
|
+
const text = node.text;
|
|
2950
|
+
return text === "App" || text.endsWith("\\App");
|
|
2951
|
+
}
|
|
2952
|
+
function collectSlimAppVars(root) {
|
|
2953
|
+
const vars = /* @__PURE__ */ new Set();
|
|
2954
|
+
walk(root, (node) => {
|
|
2955
|
+
if (node.type === "assignment_expression") {
|
|
2956
|
+
const left = node.childForFieldName("left");
|
|
2957
|
+
if (left?.type === "variable_name" && isSlimAppCtor(node.childForFieldName("right"))) {
|
|
2958
|
+
vars.add(left.text);
|
|
2959
|
+
}
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
if (node.type === "simple_parameter" && isSlimAppType(node.childForFieldName("type"))) {
|
|
2963
|
+
const name = node.childForFieldName("name");
|
|
2964
|
+
if (name?.type === "variable_name") vars.add(name.text);
|
|
2965
|
+
}
|
|
2966
|
+
});
|
|
2967
|
+
return vars;
|
|
2968
|
+
}
|
|
2969
|
+
function slimClosureParamVars(closure) {
|
|
2970
|
+
const out = ["$this"];
|
|
2971
|
+
for (let i = 0; i < closure.namedChildCount; i++) {
|
|
2972
|
+
const params = closure.namedChild(i);
|
|
2973
|
+
if (params?.type !== "formal_parameters") continue;
|
|
2974
|
+
for (let j = 0; j < params.namedChildCount; j++) {
|
|
2975
|
+
const param = params.namedChild(j);
|
|
2976
|
+
if (param?.type !== "simple_parameter") continue;
|
|
2977
|
+
for (let k = 0; k < param.namedChildCount; k++) {
|
|
2978
|
+
const v = param.namedChild(k);
|
|
2979
|
+
if (v?.type === "variable_name") {
|
|
2980
|
+
out.push(v.text);
|
|
2981
|
+
break;
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
return out;
|
|
2987
|
+
}
|
|
2988
|
+
function phpStringArray(node) {
|
|
2989
|
+
const out = [];
|
|
2990
|
+
if (node?.type !== "array_creation_expression") return out;
|
|
2991
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2992
|
+
const el = node.namedChild(i);
|
|
2993
|
+
if (el?.type !== "array_element_initializer") continue;
|
|
2994
|
+
const s = phpStaticString(el.namedChild(0));
|
|
2995
|
+
if (s !== null) out.push(s);
|
|
2996
|
+
}
|
|
2997
|
+
return out;
|
|
2998
|
+
}
|
|
2999
|
+
function slimRoutesFromSource(source, parser) {
|
|
3000
|
+
const tree = parseSource2(parser, source);
|
|
3001
|
+
const appVars = collectSlimAppVars(tree.rootNode);
|
|
3002
|
+
if (appVars.size === 0) return [];
|
|
3003
|
+
const out = [];
|
|
3004
|
+
slimWalk(tree.rootNode, "", appVars, out);
|
|
3005
|
+
return out;
|
|
3006
|
+
}
|
|
3007
|
+
function slimWalk(node, prefix, appVars, out) {
|
|
3008
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3009
|
+
const child = node.namedChild(i);
|
|
3010
|
+
if (child) slimHandle(child, prefix, appVars, out);
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
function slimHandle(node, prefix, appVars, out) {
|
|
3014
|
+
if (node.type === "member_call_expression") {
|
|
3015
|
+
const obj = node.childForFieldName("object");
|
|
3016
|
+
const method = node.childForFieldName("name")?.text;
|
|
3017
|
+
const args = node.childForFieldName("arguments");
|
|
3018
|
+
if (obj?.type === "variable_name" && method && appVars.has(obj.text)) {
|
|
3019
|
+
const line = node.startPosition.row + 1;
|
|
3020
|
+
if (method === "group") {
|
|
3021
|
+
const groupPrefix = phpFirstString(args);
|
|
3022
|
+
const closure = laravelGroupClosure(args);
|
|
3023
|
+
if (groupPrefix !== null && closure) {
|
|
3024
|
+
const inner = new Set(appVars);
|
|
3025
|
+
for (const v of slimClosureParamVars(closure)) inner.add(v);
|
|
3026
|
+
const body = closure.childForFieldName("body");
|
|
3027
|
+
if (body) slimWalk(body, laravelJoinPath(prefix, groupPrefix), inner, out);
|
|
3028
|
+
}
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3031
|
+
if (SLIM_VERBS.has(method)) {
|
|
3032
|
+
const p = phpFirstString(args);
|
|
3033
|
+
if (p !== null) {
|
|
3034
|
+
out.push({
|
|
3035
|
+
method: method.toUpperCase(),
|
|
3036
|
+
pathTemplate: laravelJoinPath(prefix, p),
|
|
3037
|
+
line,
|
|
3038
|
+
framework: "slim"
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
3041
|
+
return;
|
|
3042
|
+
}
|
|
3043
|
+
if (method === "any") {
|
|
3044
|
+
const p = phpFirstString(args);
|
|
3045
|
+
if (p !== null) {
|
|
3046
|
+
out.push({ method: "ALL", pathTemplate: laravelJoinPath(prefix, p), line, framework: "slim" });
|
|
3047
|
+
}
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
if (method === "map") {
|
|
3051
|
+
const vals = phpArgumentValues(args);
|
|
3052
|
+
const methods = phpStringArray(vals[0]);
|
|
3053
|
+
const p = vals.length > 1 ? phpStaticString(vals[1]) : null;
|
|
3054
|
+
if (p !== null) {
|
|
3055
|
+
for (const m of methods) {
|
|
3056
|
+
out.push({
|
|
3057
|
+
method: m.toUpperCase(),
|
|
3058
|
+
pathTemplate: laravelJoinPath(prefix, p),
|
|
3059
|
+
line,
|
|
3060
|
+
framework: "slim"
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
slimWalk(node, prefix, appVars, out);
|
|
3069
|
+
}
|
|
3070
|
+
var ACTIX_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "head", "options", "trace"]);
|
|
3071
|
+
function rustStringContent(node) {
|
|
3072
|
+
if (!node || node.type !== "string_literal") return null;
|
|
3073
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3074
|
+
if (node.namedChild(i)?.type === "string_content") return node.namedChild(i).text;
|
|
3075
|
+
}
|
|
3076
|
+
return "";
|
|
3077
|
+
}
|
|
3078
|
+
function actixRoutesFromSource(source, parser) {
|
|
3079
|
+
const tree = parseSource2(parser, source);
|
|
3080
|
+
const out = [];
|
|
3081
|
+
walk(tree.rootNode, (node) => {
|
|
3082
|
+
if (node.type === "attribute_item") {
|
|
3083
|
+
actixAttributeRoute(node, out);
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
if (node.type === "call_expression") {
|
|
3087
|
+
actixBuilderRoute(node, out);
|
|
3088
|
+
}
|
|
3089
|
+
});
|
|
3090
|
+
return out;
|
|
3091
|
+
}
|
|
3092
|
+
function actixAttributeRoute(attrItem, out) {
|
|
3093
|
+
const attr = attrItem.namedChild(0);
|
|
3094
|
+
if (!attr || attr.type !== "attribute") return;
|
|
3095
|
+
const nameNode = attr.namedChild(0);
|
|
3096
|
+
if (!nameNode) return;
|
|
3097
|
+
const macro = nameNode.type === "identifier" ? nameNode.text : nameNode.type === "scoped_identifier" ? nameNode.childForFieldName("name")?.text ?? null : null;
|
|
3098
|
+
if (!macro) return;
|
|
3099
|
+
const tokens = attr.childForFieldName("arguments");
|
|
3100
|
+
if (!tokens || tokens.type !== "token_tree") return;
|
|
3101
|
+
const strings = [];
|
|
3102
|
+
for (let i = 0; i < tokens.namedChildCount; i++) {
|
|
3103
|
+
const s = rustStringContent(tokens.namedChild(i));
|
|
3104
|
+
if (s !== null) strings.push(s);
|
|
3105
|
+
}
|
|
3106
|
+
const pathStr = strings[0];
|
|
3107
|
+
if (pathStr === void 0 || !pathStr.startsWith("/")) return;
|
|
3108
|
+
const line = attrItem.startPosition.row + 1;
|
|
3109
|
+
const template = canonicalizeTemplate(pathStr);
|
|
3110
|
+
if (ACTIX_METHODS.has(macro)) {
|
|
3111
|
+
out.push({ method: macro.toUpperCase(), pathTemplate: template, line, framework: "actix-web" });
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3114
|
+
if (macro === "route") {
|
|
3115
|
+
const methods = strings.slice(1).filter((m) => ACTIX_METHODS.has(m.toLowerCase()));
|
|
3116
|
+
const list = methods.length > 0 ? methods.map((m) => m.toUpperCase()) : ["ALL"];
|
|
3117
|
+
for (const m of list) {
|
|
3118
|
+
out.push({ method: m, pathTemplate: template, line, framework: "actix-web" });
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
function actixBuilderRoute(call, out) {
|
|
3123
|
+
const fn = call.childForFieldName("function");
|
|
3124
|
+
if (fn?.type !== "field_expression") return;
|
|
3125
|
+
if (fn.childForFieldName("field")?.text !== "route") return;
|
|
3126
|
+
const args = call.childForFieldName("arguments");
|
|
3127
|
+
const pathStr = rustStringContent(args?.namedChild(0));
|
|
3128
|
+
if (pathStr === null || !pathStr.startsWith("/")) return;
|
|
3129
|
+
const second = args?.namedChild(1);
|
|
3130
|
+
if (!second) return;
|
|
3131
|
+
const method = actixBuilderMethod(second);
|
|
3132
|
+
if (!method) return;
|
|
3133
|
+
out.push({
|
|
3134
|
+
method,
|
|
3135
|
+
pathTemplate: canonicalizeTemplate(pathStr),
|
|
3136
|
+
line: call.startPosition.row + 1,
|
|
3137
|
+
framework: "actix-web"
|
|
3138
|
+
});
|
|
3139
|
+
}
|
|
3140
|
+
function actixBuilderMethod(node) {
|
|
3141
|
+
let method = null;
|
|
3142
|
+
walk(node, (n) => {
|
|
3143
|
+
if (method || n.type !== "scoped_identifier") return;
|
|
3144
|
+
const verb = n.childForFieldName("name")?.text;
|
|
3145
|
+
const scopeLeaf = n.childForFieldName("path")?.text?.split("::").pop();
|
|
3146
|
+
if (scopeLeaf === "web" && verb && ACTIX_METHODS.has(verb)) method = verb.toUpperCase();
|
|
3147
|
+
});
|
|
3148
|
+
return method;
|
|
3149
|
+
}
|
|
2885
3150
|
function namedArgs(argsNode) {
|
|
2886
3151
|
const out = [];
|
|
2887
3152
|
if (!argsNode) return out;
|
|
@@ -3198,6 +3463,7 @@ async function addRoutes(graph, services) {
|
|
|
3198
3463
|
const goParser = makeGoParser2();
|
|
3199
3464
|
const rubyParser = makeRubyParser();
|
|
3200
3465
|
const phpParser = makePhpParser();
|
|
3466
|
+
const rustParser = makeRustParser();
|
|
3201
3467
|
let nodesAdded = 0;
|
|
3202
3468
|
let edgesAdded = 0;
|
|
3203
3469
|
for (const service of services) {
|
|
@@ -3220,7 +3486,10 @@ async function addRoutes(graph, services) {
|
|
|
3220
3486
|
const isGoService = service.node.language === "go";
|
|
3221
3487
|
const hasRails = deps["rails"] !== void 0;
|
|
3222
3488
|
const hasLaravel = deps["laravel/framework"] !== void 0;
|
|
3223
|
-
|
|
3489
|
+
const hasSlim = deps["slim/slim"] !== void 0;
|
|
3490
|
+
const hasSinatra = deps["sinatra"] !== void 0;
|
|
3491
|
+
const hasActix = deps["actix-web"] !== void 0;
|
|
3492
|
+
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel && !hasSlim && !hasSinatra && !hasActix)
|
|
3224
3493
|
continue;
|
|
3225
3494
|
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
3226
3495
|
const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
|
|
@@ -3231,7 +3500,8 @@ async function addRoutes(graph, services) {
|
|
|
3231
3500
|
const isGo = ext === ".go";
|
|
3232
3501
|
const isRb = ext === ".rb";
|
|
3233
3502
|
const isPhp = ext === ".php";
|
|
3234
|
-
|
|
3503
|
+
const isRs = ext === ".rs";
|
|
3504
|
+
if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy && !isGo && !isRb && !isPhp && !isRs) continue;
|
|
3235
3505
|
const relFile = toPosix(path7.relative(service.dir, file.path));
|
|
3236
3506
|
let routes;
|
|
3237
3507
|
try {
|
|
@@ -3241,8 +3511,12 @@ async function addRoutes(graph, services) {
|
|
|
3241
3511
|
phpParser,
|
|
3242
3512
|
relFile === "routes/api.php" ? "/api" : ""
|
|
3243
3513
|
) : [];
|
|
3514
|
+
if (hasSlim) routes = routes.concat(slimRoutesFromSource(file.content, phpParser));
|
|
3244
3515
|
} else if (isRb) {
|
|
3245
3516
|
routes = hasRails && relFile === "config/routes.rb" ? railsRoutesFromSource(file.content, rubyParser) : [];
|
|
3517
|
+
if (hasSinatra) routes = routes.concat(sinatraRoutesFromSource(file.content, rubyParser));
|
|
3518
|
+
} else if (isRs) {
|
|
3519
|
+
routes = hasActix ? actixRoutesFromSource(file.content, rustParser) : [];
|
|
3246
3520
|
} else if (isGo) {
|
|
3247
3521
|
if (hasGin) routes = ginRoutesFromSource(file.content, goParser);
|
|
3248
3522
|
else if (hasEcho) routes = echoRoutesFromSource(file.content, goParser);
|
|
@@ -3475,6 +3749,37 @@ function loadIncidentThresholdsFromEnv() {
|
|
|
3475
3749
|
return DEFAULT_INCIDENT_THRESHOLDS;
|
|
3476
3750
|
}
|
|
3477
3751
|
}
|
|
3752
|
+
var DEFAULT_LATENCY_STREAM_CEILING_MS = 6e4;
|
|
3753
|
+
function latencyStreamCeilingMs() {
|
|
3754
|
+
const raw = process.env.NEAT_LATENCY_STREAM_CEILING_MS;
|
|
3755
|
+
if (!raw) return DEFAULT_LATENCY_STREAM_CEILING_MS;
|
|
3756
|
+
const n = Number(raw);
|
|
3757
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
3758
|
+
console.warn(
|
|
3759
|
+
`[neat] NEAT_LATENCY_STREAM_CEILING_MS could not be parsed (${raw}); using default`
|
|
3760
|
+
);
|
|
3761
|
+
return DEFAULT_LATENCY_STREAM_CEILING_MS;
|
|
3762
|
+
}
|
|
3763
|
+
function spanServesEventStream(attrs) {
|
|
3764
|
+
for (const key of [
|
|
3765
|
+
"http.response.header.content-type",
|
|
3766
|
+
"http.response.header.content_type"
|
|
3767
|
+
]) {
|
|
3768
|
+
const v = attrs[key];
|
|
3769
|
+
const values = Array.isArray(v) ? v : v !== void 0 && v !== null ? [v] : [];
|
|
3770
|
+
for (const item of values) {
|
|
3771
|
+
if (typeof item === "string" && item.toLowerCase().includes("text/event-stream")) {
|
|
3772
|
+
return true;
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
return false;
|
|
3777
|
+
}
|
|
3778
|
+
function spanIsStreaming(span, ceilingMs = latencyStreamCeilingMs()) {
|
|
3779
|
+
if (span.websocketChannel !== void 0) return true;
|
|
3780
|
+
if (spanServesEventStream(span.attributes)) return true;
|
|
3781
|
+
return span.durationNanos > BigInt(Math.round(ceilingMs)) * 1000000n;
|
|
3782
|
+
}
|
|
3478
3783
|
function httpResponseStatusFromAttrs(attrs) {
|
|
3479
3784
|
for (const key of ["http.response.status_code", "http.status_code"]) {
|
|
3480
3785
|
const v = attrs[key];
|
|
@@ -3526,6 +3831,14 @@ function grpcStatusCodeFromAttrs(attrs) {
|
|
|
3526
3831
|
}
|
|
3527
3832
|
return void 0;
|
|
3528
3833
|
}
|
|
3834
|
+
function spanRecordsError(span) {
|
|
3835
|
+
if (span.statusCode === 2) return true;
|
|
3836
|
+
const grpc = grpcStatusCodeFromAttrs(span.attributes);
|
|
3837
|
+
if (grpc !== void 0 && grpc !== 0) return true;
|
|
3838
|
+
const httpStatus = httpResponseStatusFromAttrs(span.attributes);
|
|
3839
|
+
if (httpStatus !== void 0 && httpStatus >= 500) return true;
|
|
3840
|
+
return false;
|
|
3841
|
+
}
|
|
3529
3842
|
function nonHttpFailureMessageFromAttrs(attrs) {
|
|
3530
3843
|
const grpc = grpcStatusCodeFromAttrs(attrs);
|
|
3531
3844
|
if (grpc !== void 0 && grpc !== 0) {
|
|
@@ -4324,6 +4637,21 @@ async function recordExceptionIncident(ctx, span, ts) {
|
|
|
4324
4637
|
};
|
|
4325
4638
|
await appendErrorEvent(ctx, ev);
|
|
4326
4639
|
}
|
|
4640
|
+
async function recordGrpcFailureIncident(ctx, span, ts) {
|
|
4641
|
+
const attrs = sanitizeAttributes(span.attributes);
|
|
4642
|
+
const ev = {
|
|
4643
|
+
id: `${span.traceId}:${span.spanId}`,
|
|
4644
|
+
timestamp: ts,
|
|
4645
|
+
service: span.service,
|
|
4646
|
+
traceId: span.traceId,
|
|
4647
|
+
spanId: span.spanId,
|
|
4648
|
+
errorType: "grpc-failure",
|
|
4649
|
+
errorMessage: incidentMessage(span),
|
|
4650
|
+
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
4651
|
+
affectedNode: incidentAffectedNode(span, ctx.graph, ctx.scanPath)
|
|
4652
|
+
};
|
|
4653
|
+
await appendErrorEvent(ctx, ev);
|
|
4654
|
+
}
|
|
4327
4655
|
async function advance4xxBurst(ctx, span, affectedNode, ts, nowMs, status) {
|
|
4328
4656
|
const { threshold, windowMs } = loadIncidentThresholdsFromEnv();
|
|
4329
4657
|
if (!ctx.burstState) ctx.burstState = /* @__PURE__ */ new Map();
|
|
@@ -4367,6 +4695,12 @@ async function advance4xxBurst(ctx, span, affectedNode, ts, nowMs, status) {
|
|
|
4367
4695
|
);
|
|
4368
4696
|
ctx.burstState.delete(key);
|
|
4369
4697
|
}
|
|
4698
|
+
var NEXT_API_ROUTE_SPAN_NAME = /^executing api route \((?:pages|app)\) (\/\S*)$/;
|
|
4699
|
+
function nextApiRouteTemplate(span) {
|
|
4700
|
+
const raw = pickAttr(span, "next.span_name") ?? span.name;
|
|
4701
|
+
const match = raw ? NEXT_API_ROUTE_SPAN_NAME.exec(raw) : null;
|
|
4702
|
+
return match ? match[1] : void 0;
|
|
4703
|
+
}
|
|
4370
4704
|
function findRouteNodeByHttpRoute(graph, serviceName, method, httpRoute) {
|
|
4371
4705
|
const target = normalizePathTemplate(httpRoute);
|
|
4372
4706
|
const m = method?.toUpperCase();
|
|
@@ -4388,8 +4722,8 @@ async function handleSpan(ctx, span) {
|
|
|
4388
4722
|
warnUnidentifiedSpan(ctx.project ?? DEFAULT_PROJECT);
|
|
4389
4723
|
}
|
|
4390
4724
|
const sourceId = ensureServiceNode(ctx.graph, span.service, env);
|
|
4391
|
-
const isError = span
|
|
4392
|
-
const durationMs = span.durationNanos > 0n ? Number(span.durationNanos) / 1e6 : void 0;
|
|
4725
|
+
const isError = spanRecordsError(span);
|
|
4726
|
+
const durationMs = span.durationNanos > 0n && !spanIsStreaming(span) ? Number(span.durationNanos) / 1e6 : void 0;
|
|
4393
4727
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
4394
4728
|
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
4395
4729
|
cacheSpanService(span, nowMs, callSite);
|
|
@@ -4586,12 +4920,13 @@ async function handleSpan(ctx, span) {
|
|
|
4586
4920
|
}
|
|
4587
4921
|
}
|
|
4588
4922
|
}
|
|
4589
|
-
|
|
4923
|
+
const fusionRoute = nextApiRouteTemplate(span) ?? span.httpRoute;
|
|
4924
|
+
if (fusionRoute && (span.kind === 2 || span.kind === 1 || span.kind === 0 || span.kind === void 0)) {
|
|
4590
4925
|
const routeNodeId = findRouteNodeByHttpRoute(
|
|
4591
4926
|
ctx.graph,
|
|
4592
4927
|
span.service,
|
|
4593
4928
|
span.httpMethod,
|
|
4594
|
-
|
|
4929
|
+
fusionRoute
|
|
4595
4930
|
);
|
|
4596
4931
|
if (routeNodeId) {
|
|
4597
4932
|
const routeSvc = ctx.graph.getNodeAttributes(routeNodeId).service;
|
|
@@ -4623,10 +4958,13 @@ async function handleSpan(ctx, span) {
|
|
|
4623
4958
|
}
|
|
4624
4959
|
if (span.statusCode !== 2) {
|
|
4625
4960
|
const status = httpResponseStatus(span);
|
|
4961
|
+
const grpcStatus = grpcStatusCodeFromAttrs(span.attributes);
|
|
4626
4962
|
if (span.exception) {
|
|
4627
4963
|
await recordExceptionIncident(ctx, span, ts);
|
|
4628
4964
|
} else if (status !== void 0 && status >= 500) {
|
|
4629
4965
|
await recordFailingResponseIncident(ctx, span, sourceId, ts, status, 1);
|
|
4966
|
+
} else if (grpcStatus !== void 0 && grpcStatus !== 0) {
|
|
4967
|
+
await recordGrpcFailureIncident(ctx, span, ts);
|
|
4630
4968
|
} else if (status !== void 0 && status >= 400 && spanMintsObservedEdge(span.kind)) {
|
|
4631
4969
|
await advance4xxBurst(ctx, span, sourceId, ts, nowMs, status);
|
|
4632
4970
|
}
|
|
@@ -5008,19 +5346,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
5008
5346
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
5009
5347
|
let best = { path: [start], edges: [] };
|
|
5010
5348
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
5011
|
-
function step(node,
|
|
5012
|
-
if (
|
|
5013
|
-
best = { path: [...
|
|
5349
|
+
function step(node, path72, edges) {
|
|
5350
|
+
if (path72.length > best.path.length) {
|
|
5351
|
+
best = { path: [...path72], edges: [...edges] };
|
|
5014
5352
|
}
|
|
5015
|
-
if (
|
|
5353
|
+
if (path72.length - 1 >= maxDepth) return;
|
|
5016
5354
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
5017
5355
|
for (const [srcId, edge] of incoming) {
|
|
5018
5356
|
if (visited.has(srcId)) continue;
|
|
5019
5357
|
visited.add(srcId);
|
|
5020
|
-
|
|
5358
|
+
path72.push(srcId);
|
|
5021
5359
|
edges.push(edge);
|
|
5022
|
-
step(srcId,
|
|
5023
|
-
|
|
5360
|
+
step(srcId, path72, edges);
|
|
5361
|
+
path72.pop();
|
|
5024
5362
|
edges.pop();
|
|
5025
5363
|
visited.delete(srcId);
|
|
5026
5364
|
}
|
|
@@ -5028,11 +5366,11 @@ function longestIncomingWalk(graph, start, maxDepth) {
|
|
|
5028
5366
|
step(start, [start], []);
|
|
5029
5367
|
return best;
|
|
5030
5368
|
}
|
|
5031
|
-
function databaseRootCauseShape(graph, origin,
|
|
5369
|
+
function databaseRootCauseShape(graph, origin, walk10) {
|
|
5032
5370
|
const targetDb = origin;
|
|
5033
5371
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
5034
5372
|
if (candidatePairs.length === 0) return null;
|
|
5035
|
-
for (const id of
|
|
5373
|
+
for (const id of walk10.path) {
|
|
5036
5374
|
const owner = resolveOwningService(graph, id);
|
|
5037
5375
|
if (!owner) continue;
|
|
5038
5376
|
const { id: serviceId15, svc } = owner;
|
|
@@ -5059,8 +5397,8 @@ function databaseRootCauseShape(graph, origin, walk9) {
|
|
|
5059
5397
|
}
|
|
5060
5398
|
return null;
|
|
5061
5399
|
}
|
|
5062
|
-
function serviceRootCauseShape(graph, _origin,
|
|
5063
|
-
for (const id of
|
|
5400
|
+
function serviceRootCauseShape(graph, _origin, walk10) {
|
|
5401
|
+
for (const id of walk10.path) {
|
|
5064
5402
|
const owner = resolveOwningService(graph, id);
|
|
5065
5403
|
if (!owner) continue;
|
|
5066
5404
|
const { id: serviceId15, svc } = owner;
|
|
@@ -5096,15 +5434,15 @@ function serviceRootCauseShape(graph, _origin, walk9) {
|
|
|
5096
5434
|
}
|
|
5097
5435
|
return null;
|
|
5098
5436
|
}
|
|
5099
|
-
function fileRootCauseShape(graph, origin,
|
|
5437
|
+
function fileRootCauseShape(graph, origin, walk10) {
|
|
5100
5438
|
const owner = resolveOwningService(graph, origin.id);
|
|
5101
5439
|
if (!owner) return null;
|
|
5102
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
5440
|
+
return serviceRootCauseShape(graph, owner.svc, walk10);
|
|
5103
5441
|
}
|
|
5104
|
-
function symbolRootCauseShape(graph, origin,
|
|
5442
|
+
function symbolRootCauseShape(graph, origin, walk10) {
|
|
5105
5443
|
const owner = resolveOwningService(graph, origin.id);
|
|
5106
5444
|
if (!owner) return null;
|
|
5107
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
5445
|
+
return serviceRootCauseShape(graph, owner.svc, walk10);
|
|
5108
5446
|
}
|
|
5109
5447
|
var rootCauseShapes = {
|
|
5110
5448
|
[NodeType5.DatabaseNode]: databaseRootCauseShape,
|
|
@@ -5117,25 +5455,29 @@ function legacyRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
5117
5455
|
const origin = graph.getNodeAttributes(errorNodeId);
|
|
5118
5456
|
const shape = rootCauseShapes[origin.type];
|
|
5119
5457
|
if (shape) {
|
|
5120
|
-
const
|
|
5121
|
-
const match = shape(graph, origin,
|
|
5458
|
+
const walk10 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
|
|
5459
|
+
const match = shape(graph, origin, walk10);
|
|
5122
5460
|
if (match) {
|
|
5123
5461
|
const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
|
|
5124
|
-
return
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5462
|
+
return {
|
|
5463
|
+
source: "compat",
|
|
5464
|
+
result: RootCauseResultSchema.parse({
|
|
5465
|
+
rootCauseNode: match.rootCauseNode,
|
|
5466
|
+
rootCauseReason: reason,
|
|
5467
|
+
traversalPath: walk10.path,
|
|
5468
|
+
edgeProvenances: walk10.edges.map((e) => e.provenance),
|
|
5469
|
+
confidence: confidenceFromMix(walk10.edges),
|
|
5470
|
+
fixRecommendation: match.fixRecommendation
|
|
5471
|
+
})
|
|
5472
|
+
};
|
|
5132
5473
|
}
|
|
5133
5474
|
}
|
|
5134
5475
|
if (origin.type === NodeType5.ServiceNode) {
|
|
5135
5476
|
const crossService = crossServiceRootCause(graph, errorNodeId, incidents, errorEvent);
|
|
5136
|
-
if (crossService) return crossService;
|
|
5477
|
+
if (crossService) return { result: crossService, source: "cross-service" };
|
|
5137
5478
|
}
|
|
5138
|
-
|
|
5479
|
+
const incident = rootCauseFromIncidents(errorNodeId, incidents, errorEvent);
|
|
5480
|
+
return incident ? { result: incident, source: "incident" } : null;
|
|
5139
5481
|
}
|
|
5140
5482
|
var INCIDENT_ROOT_CAUSE_CONFIDENCE = 0.6;
|
|
5141
5483
|
function incidentMatchesNode(ev, nodeId) {
|
|
@@ -5231,26 +5573,75 @@ function dominantFailingCall(graph, serviceId15, visited) {
|
|
|
5231
5573
|
return best;
|
|
5232
5574
|
}
|
|
5233
5575
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
5234
|
-
const
|
|
5576
|
+
const path72 = [originServiceId];
|
|
5235
5577
|
const edges = [];
|
|
5236
5578
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
5237
5579
|
let current = originServiceId;
|
|
5238
5580
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
5239
5581
|
const hop = dominantFailingCall(graph, current, visited);
|
|
5240
5582
|
if (!hop) break;
|
|
5241
|
-
|
|
5583
|
+
path72.push(hop.nextService);
|
|
5242
5584
|
edges.push(hop.edge);
|
|
5243
5585
|
visited.add(hop.nextService);
|
|
5244
5586
|
current = hop.nextService;
|
|
5245
5587
|
}
|
|
5246
5588
|
if (edges.length === 0) return null;
|
|
5247
|
-
return { path:
|
|
5589
|
+
return { path: path72, edges, culprit: current };
|
|
5590
|
+
}
|
|
5591
|
+
function isStaleCallEdge(e) {
|
|
5592
|
+
return e.type === EdgeType6.CALLS && e.provenance === Provenance6.STALE;
|
|
5593
|
+
}
|
|
5594
|
+
function staleCallDominates(e, id, curEdge, curId) {
|
|
5595
|
+
const ev = e.signal?.spanCount ?? e.callCount ?? 0;
|
|
5596
|
+
const cv = curEdge.signal?.spanCount ?? curEdge.callCount ?? 0;
|
|
5597
|
+
if (ev !== cv) return ev > cv;
|
|
5598
|
+
return id < curId;
|
|
5599
|
+
}
|
|
5600
|
+
function dominantStaleCall(graph, serviceId15, visited) {
|
|
5601
|
+
const bestByCallee = /* @__PURE__ */ new Map();
|
|
5602
|
+
for (const src of callSourcesForService(graph, serviceId15)) {
|
|
5603
|
+
for (const edgeId of graph.outboundEdges(src)) {
|
|
5604
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
5605
|
+
if (e.type !== EdgeType6.CALLS) continue;
|
|
5606
|
+
if (isFrontierNode(graph, e.target)) continue;
|
|
5607
|
+
const owner = resolveOwningService(graph, e.target);
|
|
5608
|
+
if (!owner || visited.has(owner.id)) continue;
|
|
5609
|
+
const cur = bestByCallee.get(owner.id);
|
|
5610
|
+
if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {
|
|
5611
|
+
bestByCallee.set(owner.id, e);
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
}
|
|
5615
|
+
let best = null;
|
|
5616
|
+
for (const [id, edge] of bestByCallee) {
|
|
5617
|
+
if (!isStaleCallEdge(edge)) continue;
|
|
5618
|
+
if (!best || staleCallDominates(edge, id, best.edge, best.nextService)) {
|
|
5619
|
+
best = { nextService: id, edge };
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
return best;
|
|
5623
|
+
}
|
|
5624
|
+
function followStaleCallChain(graph, originServiceId, maxDepth) {
|
|
5625
|
+
const path72 = [originServiceId];
|
|
5626
|
+
const edges = [];
|
|
5627
|
+
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
5628
|
+
let current = originServiceId;
|
|
5629
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
5630
|
+
const hop = dominantStaleCall(graph, current, visited);
|
|
5631
|
+
if (!hop) break;
|
|
5632
|
+
path72.push(hop.nextService);
|
|
5633
|
+
edges.push(hop.edge);
|
|
5634
|
+
visited.add(hop.nextService);
|
|
5635
|
+
current = hop.nextService;
|
|
5636
|
+
}
|
|
5637
|
+
if (edges.length === 0) return null;
|
|
5638
|
+
return { path: path72, edges, culprit: current };
|
|
5248
5639
|
}
|
|
5249
5640
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
5250
5641
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
5251
5642
|
if (!chain) return null;
|
|
5252
5643
|
const culprit = chain.culprit;
|
|
5253
|
-
const
|
|
5644
|
+
const path72 = [...chain.path];
|
|
5254
5645
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
5255
5646
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
5256
5647
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -5258,14 +5649,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5258
5649
|
if (loc) {
|
|
5259
5650
|
let rootCauseNode = culprit;
|
|
5260
5651
|
if (loc.fileNode) {
|
|
5261
|
-
|
|
5652
|
+
path72.push(loc.fileNode);
|
|
5262
5653
|
edgeProvenances.push(Provenance6.OBSERVED);
|
|
5263
5654
|
rootCauseNode = loc.fileNode;
|
|
5264
5655
|
}
|
|
5265
5656
|
return RootCauseResultSchema.parse({
|
|
5266
5657
|
rootCauseNode,
|
|
5267
5658
|
rootCauseReason: loc.rootCauseReason,
|
|
5268
|
-
traversalPath:
|
|
5659
|
+
traversalPath: path72,
|
|
5269
5660
|
edgeProvenances,
|
|
5270
5661
|
confidence,
|
|
5271
5662
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -5277,7 +5668,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5277
5668
|
return RootCauseResultSchema.parse({
|
|
5278
5669
|
rootCauseNode: culprit,
|
|
5279
5670
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
5280
|
-
traversalPath:
|
|
5671
|
+
traversalPath: path72,
|
|
5281
5672
|
edgeProvenances,
|
|
5282
5673
|
confidence,
|
|
5283
5674
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -5664,17 +6055,20 @@ function displayNameOf(nodeId) {
|
|
|
5664
6055
|
return nodeId.replace(/^[a-z]+:/, "");
|
|
5665
6056
|
}
|
|
5666
6057
|
function getRootCause(graph, errorNodeId, errorEvent, incidents, opts) {
|
|
5667
|
-
const
|
|
5668
|
-
if (!
|
|
6058
|
+
const tagged = legacyRootCause(graph, errorNodeId, errorEvent, incidents);
|
|
6059
|
+
if (!tagged) return null;
|
|
5669
6060
|
const navigation = opts?.navigation ?? process.env.NEAT_RCA_NAVIGATION !== "0";
|
|
5670
|
-
if (!navigation) return
|
|
5671
|
-
return enrichWithNavigation(graph, errorNodeId,
|
|
6061
|
+
if (!navigation) return tagged.result;
|
|
6062
|
+
return enrichWithNavigation(graph, errorNodeId, tagged, incidents, opts?.now ?? Date.now());
|
|
5672
6063
|
}
|
|
5673
|
-
function enrichWithNavigation(graph, errorNodeId,
|
|
6064
|
+
function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
6065
|
+
const legacy = tagged.result;
|
|
5674
6066
|
const seedNode = legacy.rootCauseNode;
|
|
5675
6067
|
const seedCtx = graph.hasNode(seedNode) ? nodeContext(graph, seedNode, incidents, now) : null;
|
|
5676
6068
|
const lastProv = legacy.edgeProvenances[legacy.edgeProvenances.length - 1];
|
|
5677
6069
|
const candidates = [];
|
|
6070
|
+
const deadEndOnSymptom = tagged.source === "incident" && seedNode === errorNodeId && legacy.traversalPath.length === 1;
|
|
6071
|
+
const staleChain = deadEndOnSymptom && !(seedCtx && isVictimSeed(seedCtx)) ? followStaleCallChain(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH) : null;
|
|
5678
6072
|
if (seedCtx && isVictimSeed(seedCtx)) {
|
|
5679
6073
|
const origin = findLoadOrigin(graph, errorNodeId, incidents, now);
|
|
5680
6074
|
const staleNote = seedCtx.stale ? "; it has gone STALE under load" : "";
|
|
@@ -5699,6 +6093,27 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
|
|
|
5699
6093
|
confidence: Math.min(legacy.confidence, 0.4),
|
|
5700
6094
|
...lastProv ? { provenance: lastProv } : {}
|
|
5701
6095
|
});
|
|
6096
|
+
} else if (staleChain) {
|
|
6097
|
+
const culprit = staleChain.culprit;
|
|
6098
|
+
const culpritName = displayNameOf(culprit);
|
|
6099
|
+
const seedName = displayNameOf(seedNode);
|
|
6100
|
+
const staleConfidence = confidenceFromMix(staleChain.edges, now);
|
|
6101
|
+
candidates.push({
|
|
6102
|
+
node: culprit,
|
|
6103
|
+
classification: "primary-failure",
|
|
6104
|
+
reason: `${culpritName} is the stale-derived root cause (low confidence): live telemetry for this subgraph has gone quiet, but the last-observed topology traces the failure surfacing at ${seedName} downstream through a STALE call chain to ${culpritName}. Provenance is STALE, so confidence is capped low \u2014 restore instrumentation and re-run to confirm before acting.`,
|
|
6105
|
+
context: nodeContext(graph, culprit, incidents, now),
|
|
6106
|
+
confidence: staleConfidence,
|
|
6107
|
+
provenance: Provenance6.STALE
|
|
6108
|
+
});
|
|
6109
|
+
candidates.push({
|
|
6110
|
+
node: seedNode,
|
|
6111
|
+
classification: "symptom-only",
|
|
6112
|
+
reason: `The failure surfaced here, but the only causal chain the graph still holds is STALE and runs downstream \u2014 ${seedName} is the surface of a stale-traced failure, not a proven origin.`,
|
|
6113
|
+
context: seedCtx ?? EMPTY_CONTEXT,
|
|
6114
|
+
confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.STALE),
|
|
6115
|
+
...lastProv ? { provenance: lastProv } : {}
|
|
6116
|
+
});
|
|
5702
6117
|
} else {
|
|
5703
6118
|
candidates.push({
|
|
5704
6119
|
node: seedNode,
|
|
@@ -5712,11 +6127,14 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
|
|
|
5712
6127
|
const top = candidates[0];
|
|
5713
6128
|
let traversalPath = legacy.traversalPath;
|
|
5714
6129
|
let edgeProvenances = legacy.edgeProvenances;
|
|
5715
|
-
if (top.node
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
6130
|
+
if (staleChain && top.node === staleChain.culprit) {
|
|
6131
|
+
traversalPath = staleChain.path;
|
|
6132
|
+
edgeProvenances = staleChain.edges.map((e) => e.provenance);
|
|
6133
|
+
} else if (top.node !== seedNode) {
|
|
6134
|
+
const path72 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
|
|
6135
|
+
if (path72) {
|
|
6136
|
+
traversalPath = path72.nodes;
|
|
6137
|
+
edgeProvenances = path72.edges.map((e) => e.provenance);
|
|
5720
6138
|
} else {
|
|
5721
6139
|
traversalPath = [errorNodeId, top.node];
|
|
5722
6140
|
edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
|
|
@@ -5738,6 +6156,9 @@ function fixRecommendationForTop(top, seedNode, legacy) {
|
|
|
5738
6156
|
return legacy.fixRecommendation;
|
|
5739
6157
|
}
|
|
5740
6158
|
const name = top.node.replace(/^service:/, "");
|
|
6159
|
+
if (top.provenance === Provenance6.STALE) {
|
|
6160
|
+
return `Live telemetry for this path has gone quiet; the last-observed topology traces the failure downstream to ${name}. Restore instrumentation (or re-run with live traces) to confirm, then inspect ${name}.`;
|
|
6161
|
+
}
|
|
5741
6162
|
if (top.classification === "primary-failure") {
|
|
5742
6163
|
return `Reduce or throttle the load from ${name} (or scale the saturated downstream capacity it drives) \u2014 the failure originates at this overloading source, not the starved callee.`;
|
|
5743
6164
|
}
|
|
@@ -6827,7 +7248,7 @@ import Php2 from "tree-sitter-php";
|
|
|
6827
7248
|
import CSharp from "tree-sitter-c-sharp";
|
|
6828
7249
|
import Java from "tree-sitter-java";
|
|
6829
7250
|
import Kotlin from "tree-sitter-kotlin";
|
|
6830
|
-
import
|
|
7251
|
+
import Rust2 from "tree-sitter-rust";
|
|
6831
7252
|
import Cpp from "tree-sitter-cpp";
|
|
6832
7253
|
import {
|
|
6833
7254
|
EdgeType as EdgeType7,
|
|
@@ -6855,7 +7276,7 @@ var SYMBOL_GRAMMAR_BY_EXT = {
|
|
|
6855
7276
|
".cs": CSharp,
|
|
6856
7277
|
".java": Java,
|
|
6857
7278
|
".kt": Kotlin,
|
|
6858
|
-
".rs":
|
|
7279
|
+
".rs": Rust2,
|
|
6859
7280
|
// C++ (ADR-202) — only the UNAMBIGUOUS extensions. `.cpp` / `.cc` / `.cxx` /
|
|
6860
7281
|
// `.c++` are implementation files; `.hpp` / `.hh` / `.hxx` / `.h++` are C++-only
|
|
6861
7282
|
// headers. `.h` and `.c` are deliberately absent: they are shared with C (a
|
|
@@ -7295,15 +7716,15 @@ function collectKotlinSymbolDefs(root) {
|
|
|
7295
7716
|
});
|
|
7296
7717
|
};
|
|
7297
7718
|
const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
|
|
7298
|
-
const
|
|
7719
|
+
const firstChildOfType2 = (node, types) => {
|
|
7299
7720
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
7300
7721
|
const child = node.namedChild(i);
|
|
7301
7722
|
if (child && types.includes(child.type)) return child;
|
|
7302
7723
|
}
|
|
7303
7724
|
return void 0;
|
|
7304
7725
|
};
|
|
7305
|
-
const nameOf = (node, ...types) =>
|
|
7306
|
-
const bodyOf = (node) =>
|
|
7726
|
+
const nameOf = (node, ...types) => firstChildOfType2(node, types)?.text;
|
|
7727
|
+
const bodyOf = (node) => firstChildOfType2(node, ["class_body", "enum_class_body"]);
|
|
7307
7728
|
let pkg;
|
|
7308
7729
|
for (let i = 0; i < root.namedChildCount; i++) {
|
|
7309
7730
|
const child = root.namedChild(i);
|
|
@@ -7810,7 +8231,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
7810
8231
|
return best;
|
|
7811
8232
|
};
|
|
7812
8233
|
const requests = [];
|
|
7813
|
-
const
|
|
8234
|
+
const walk10 = (node) => {
|
|
7814
8235
|
if (node.type === "class_declaration" || node.type === "abstract_class_declaration" || node.type === "class") {
|
|
7815
8236
|
const self = localBySpan.get(`${node.startPosition.row + 1}:${node.endPosition.row + 1}`);
|
|
7816
8237
|
if (self && self.kind === "class") {
|
|
@@ -7856,10 +8277,10 @@ async function addSymbolEdges(graph, services) {
|
|
|
7856
8277
|
}
|
|
7857
8278
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
7858
8279
|
const child = node.namedChild(i);
|
|
7859
|
-
if (child)
|
|
8280
|
+
if (child) walk10(child);
|
|
7860
8281
|
}
|
|
7861
8282
|
};
|
|
7862
|
-
|
|
8283
|
+
walk10(root);
|
|
7863
8284
|
for (const req of requests) {
|
|
7864
8285
|
const targetSid = resolveTarget(req.targetName, req.wantKind);
|
|
7865
8286
|
if (!targetSid) continue;
|
|
@@ -8160,7 +8581,7 @@ async function addServerActions(graph, services) {
|
|
|
8160
8581
|
}
|
|
8161
8582
|
|
|
8162
8583
|
// src/extract/databases/index.ts
|
|
8163
|
-
import
|
|
8584
|
+
import path34 from "path";
|
|
8164
8585
|
import {
|
|
8165
8586
|
EdgeType as EdgeType10,
|
|
8166
8587
|
NodeType as NodeType20,
|
|
@@ -8593,8 +9014,161 @@ async function parse8(serviceDir) {
|
|
|
8593
9014
|
}
|
|
8594
9015
|
var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
8595
9016
|
|
|
8596
|
-
// src/extract/databases/
|
|
9017
|
+
// src/extract/databases/csharp.ts
|
|
9018
|
+
import { promises as fs22 } from "fs";
|
|
8597
9019
|
import path32 from "path";
|
|
9020
|
+
var CS_EXT = ".cs";
|
|
9021
|
+
var NPGSQL_GATE = /\bUseNpgsql\b|\bNpgsql\b/;
|
|
9022
|
+
var REDIS_GATE = /\bConnectionMultiplexer\b|\bStackExchange\.Redis\b|\bConfigurationOptions\.Parse\b|\bAddStackExchangeRedisCache\b/;
|
|
9023
|
+
var ENV_READ_RE = /(?:GetEnvironmentVariable|GetConnectionString)\(\s*"([^"]+)"\s*\)|Configuration\s*\[\s*"([^"]+)"\s*\]/g;
|
|
9024
|
+
var STRING_LITERAL_RE = /@?"([^"\\]*(?:\\.[^"\\]*)*)"/g;
|
|
9025
|
+
function hostIsUnresolved(host) {
|
|
9026
|
+
return host === "" || /[${}]/.test(host);
|
|
9027
|
+
}
|
|
9028
|
+
function looksLikePostgres(s) {
|
|
9029
|
+
return /(?:^|;)\s*(?:host|server|data\s*source)\s*=/i.test(s) || /^postgres(?:ql)?:\/\//i.test(s);
|
|
9030
|
+
}
|
|
9031
|
+
function looksLikeRedis(s) {
|
|
9032
|
+
return /^rediss?:\/\//i.test(s) || /^[A-Za-z0-9_.-]+:\d+(?:$|,)/.test(s) || /,\s*(?:ssl|abortconnect|allowadmin|connecttimeout|password|user)\s*=/i.test(s);
|
|
9033
|
+
}
|
|
9034
|
+
function parsePostgresConnection(raw) {
|
|
9035
|
+
const s = raw.trim();
|
|
9036
|
+
if (/^postgres(?:ql)?:\/\//i.test(s)) return parseConnectionString(s);
|
|
9037
|
+
const fields = /* @__PURE__ */ new Map();
|
|
9038
|
+
for (const part of s.split(";")) {
|
|
9039
|
+
const eq = part.indexOf("=");
|
|
9040
|
+
if (eq < 0) continue;
|
|
9041
|
+
const key = part.slice(0, eq).trim().toLowerCase().replace(/\s+/g, " ");
|
|
9042
|
+
const value = part.slice(eq + 1).trim();
|
|
9043
|
+
if (value && !fields.has(key)) fields.set(key, value);
|
|
9044
|
+
}
|
|
9045
|
+
const hostRaw = fields.get("host") ?? fields.get("server") ?? fields.get("data source");
|
|
9046
|
+
if (!hostRaw) return null;
|
|
9047
|
+
const host = hostRaw.split(",")[0].trim();
|
|
9048
|
+
if (hostIsUnresolved(host)) return null;
|
|
9049
|
+
const portRaw = fields.get("port");
|
|
9050
|
+
const port = portRaw && /^\d+$/.test(portRaw) ? Number(portRaw) : void 0;
|
|
9051
|
+
const database = fields.get("database") ?? fields.get("db") ?? "";
|
|
9052
|
+
return { host, port, database, engine: "postgresql", engineVersion: "unknown" };
|
|
9053
|
+
}
|
|
9054
|
+
function parseRedisEndpoint(raw) {
|
|
9055
|
+
const s = raw.trim();
|
|
9056
|
+
if (/^rediss?:\/\//i.test(s)) return parseConnectionString(s);
|
|
9057
|
+
const first = s.split(",")[0].trim();
|
|
9058
|
+
const m = first.match(/^([A-Za-z0-9_.-]+)(?::(\d+))?$/);
|
|
9059
|
+
if (!m) return null;
|
|
9060
|
+
const host = m[1];
|
|
9061
|
+
if (hostIsUnresolved(host) || host.includes("=")) return null;
|
|
9062
|
+
const port = m[2] ? Number(m[2]) : void 0;
|
|
9063
|
+
return { host, port, database: "", engine: "redis", engineVersion: "unknown" };
|
|
9064
|
+
}
|
|
9065
|
+
async function resolveEnvUpTree(startDir, name) {
|
|
9066
|
+
let dir = path32.resolve(startDir);
|
|
9067
|
+
for (let depth = 0; depth < 12; depth++) {
|
|
9068
|
+
const value = await resolveEnvVar(dir, name);
|
|
9069
|
+
if (value !== null) return value;
|
|
9070
|
+
const atRepoRoot = await fs22.access(path32.join(dir, ".git")).then(() => true).catch(() => false);
|
|
9071
|
+
const parent = path32.dirname(dir);
|
|
9072
|
+
if (atRepoRoot || parent === dir) break;
|
|
9073
|
+
dir = parent;
|
|
9074
|
+
}
|
|
9075
|
+
return null;
|
|
9076
|
+
}
|
|
9077
|
+
async function interpolateEnvRefs(value, dir) {
|
|
9078
|
+
const refs = /* @__PURE__ */ new Set();
|
|
9079
|
+
for (const m of value.matchAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g)) {
|
|
9080
|
+
refs.add(m[1] ?? m[2]);
|
|
9081
|
+
}
|
|
9082
|
+
let out = value;
|
|
9083
|
+
for (const name of refs) {
|
|
9084
|
+
const resolved = await resolveEnvUpTree(dir, name);
|
|
9085
|
+
if (resolved === null) continue;
|
|
9086
|
+
out = out.split(`\${${name}}`).join(resolved).replace(new RegExp(`\\$${name}\\b`, "g"), resolved);
|
|
9087
|
+
}
|
|
9088
|
+
return out;
|
|
9089
|
+
}
|
|
9090
|
+
function stringLiterals(masked) {
|
|
9091
|
+
const out = [];
|
|
9092
|
+
STRING_LITERAL_RE.lastIndex = 0;
|
|
9093
|
+
let m;
|
|
9094
|
+
while ((m = STRING_LITERAL_RE.exec(masked)) !== null) out.push(m[1]);
|
|
9095
|
+
return out;
|
|
9096
|
+
}
|
|
9097
|
+
function envKeys(masked) {
|
|
9098
|
+
const out = [];
|
|
9099
|
+
ENV_READ_RE.lastIndex = 0;
|
|
9100
|
+
let m;
|
|
9101
|
+
while ((m = ENV_READ_RE.exec(masked)) !== null) {
|
|
9102
|
+
const key = m[1] ?? m[2];
|
|
9103
|
+
if (key) out.push(key);
|
|
9104
|
+
}
|
|
9105
|
+
return out;
|
|
9106
|
+
}
|
|
9107
|
+
async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
|
|
9108
|
+
const out = [];
|
|
9109
|
+
for (const key of keys) {
|
|
9110
|
+
const raw = await resolveEnvUpTree(serviceDir, key);
|
|
9111
|
+
if (raw === null) continue;
|
|
9112
|
+
const value = await interpolateEnvRefs(raw, serviceDir);
|
|
9113
|
+
if (!looksLike(value)) continue;
|
|
9114
|
+
const parsed = parse11(value);
|
|
9115
|
+
if (parsed) out.push(parsed);
|
|
9116
|
+
}
|
|
9117
|
+
for (const lit of literals) {
|
|
9118
|
+
if (!looksLike(lit)) continue;
|
|
9119
|
+
const parsed = parse11(lit);
|
|
9120
|
+
if (parsed) out.push(parsed);
|
|
9121
|
+
}
|
|
9122
|
+
return out;
|
|
9123
|
+
}
|
|
9124
|
+
async function parse9(serviceDir) {
|
|
9125
|
+
const files = (await walkSourceFiles(serviceDir).catch(() => [])).filter(
|
|
9126
|
+
(f) => path32.extname(f) === CS_EXT
|
|
9127
|
+
);
|
|
9128
|
+
if (files.length === 0) return [];
|
|
9129
|
+
const sources = [];
|
|
9130
|
+
for (const file of files) {
|
|
9131
|
+
const content = await fs22.readFile(file, "utf8").catch(() => null);
|
|
9132
|
+
if (content !== null) sources.push({ file, content });
|
|
9133
|
+
}
|
|
9134
|
+
let pgGateFile = null;
|
|
9135
|
+
let redisGateFile = null;
|
|
9136
|
+
for (const { file, content } of sources) {
|
|
9137
|
+
if (pgGateFile === null && NPGSQL_GATE.test(content)) pgGateFile = file;
|
|
9138
|
+
if (redisGateFile === null && REDIS_GATE.test(content)) redisGateFile = file;
|
|
9139
|
+
}
|
|
9140
|
+
if (!pgGateFile && !redisGateFile) return [];
|
|
9141
|
+
const literals = [];
|
|
9142
|
+
const keys = [];
|
|
9143
|
+
for (const { content } of sources) {
|
|
9144
|
+
const masked = maskCommentsInSource(content);
|
|
9145
|
+
literals.push(...stringLiterals(masked));
|
|
9146
|
+
keys.push(...envKeys(masked));
|
|
9147
|
+
}
|
|
9148
|
+
const out = [];
|
|
9149
|
+
const seenHosts = /* @__PURE__ */ new Set();
|
|
9150
|
+
const push = (config, sourceFile) => {
|
|
9151
|
+
const dedupe = `${config.engine}:${config.host}`;
|
|
9152
|
+
if (seenHosts.has(dedupe)) return;
|
|
9153
|
+
seenHosts.add(dedupe);
|
|
9154
|
+
out.push({ ...config, sourceFile });
|
|
9155
|
+
};
|
|
9156
|
+
if (pgGateFile) {
|
|
9157
|
+
for (const pg3 of await resolveConfigs(literals, keys, serviceDir, looksLikePostgres, parsePostgresConnection)) {
|
|
9158
|
+
push(pg3, pgGateFile);
|
|
9159
|
+
}
|
|
9160
|
+
}
|
|
9161
|
+
if (redisGateFile) {
|
|
9162
|
+
for (const redis of await resolveConfigs(literals, keys, serviceDir, looksLikeRedis, parseRedisEndpoint)) {
|
|
9163
|
+
push(redis, redisGateFile);
|
|
9164
|
+
}
|
|
9165
|
+
}
|
|
9166
|
+
return out;
|
|
9167
|
+
}
|
|
9168
|
+
var csharpParser = { name: "csharp", parse: parse9 };
|
|
9169
|
+
|
|
9170
|
+
// src/extract/databases/docker-compose.ts
|
|
9171
|
+
import path33 from "path";
|
|
8598
9172
|
function portFromService(svc) {
|
|
8599
9173
|
for (const raw of svc.ports ?? []) {
|
|
8600
9174
|
const str = String(raw);
|
|
@@ -8619,9 +9193,9 @@ function databaseFromEnv(svc) {
|
|
|
8619
9193
|
};
|
|
8620
9194
|
return get("POSTGRES_DB") ?? get("MYSQL_DATABASE") ?? get("MONGO_INITDB_DATABASE") ?? "";
|
|
8621
9195
|
}
|
|
8622
|
-
async function
|
|
9196
|
+
async function parse10(serviceDir) {
|
|
8623
9197
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
8624
|
-
const abs =
|
|
9198
|
+
const abs = path33.join(serviceDir, name);
|
|
8625
9199
|
if (!await exists(abs)) continue;
|
|
8626
9200
|
const raw = await readYaml(abs);
|
|
8627
9201
|
if (!raw?.services) return [];
|
|
@@ -8643,7 +9217,7 @@ async function parse9(serviceDir) {
|
|
|
8643
9217
|
}
|
|
8644
9218
|
return [];
|
|
8645
9219
|
}
|
|
8646
|
-
var dockerComposeParser = { name: "docker-compose", parse:
|
|
9220
|
+
var dockerComposeParser = { name: "docker-compose", parse: parse10 };
|
|
8647
9221
|
|
|
8648
9222
|
// src/extract/databases/index.ts
|
|
8649
9223
|
var DB_PARSERS = [
|
|
@@ -8655,6 +9229,7 @@ var DB_PARSERS = [
|
|
|
8655
9229
|
ormconfigParser,
|
|
8656
9230
|
typeormParser,
|
|
8657
9231
|
sequelizeParser,
|
|
9232
|
+
csharpParser,
|
|
8658
9233
|
dockerComposeParser
|
|
8659
9234
|
];
|
|
8660
9235
|
function compatibleDriversFor(engine) {
|
|
@@ -8793,7 +9368,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
8793
9368
|
discoveredVia: mergedDiscoveredVia
|
|
8794
9369
|
});
|
|
8795
9370
|
}
|
|
8796
|
-
const relConfigFile = toPosix(
|
|
9371
|
+
const relConfigFile = toPosix(path34.relative(service.dir, config.sourceFile));
|
|
8797
9372
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
8798
9373
|
graph,
|
|
8799
9374
|
service.pkg.name,
|
|
@@ -8802,7 +9377,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
8802
9377
|
);
|
|
8803
9378
|
nodesAdded += fn;
|
|
8804
9379
|
edgesAdded += fe;
|
|
8805
|
-
const evidenceFile = toPosix(
|
|
9380
|
+
const evidenceFile = toPosix(path34.relative(scanPath, config.sourceFile));
|
|
8806
9381
|
const edge = {
|
|
8807
9382
|
id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
|
|
8808
9383
|
source: fileNodeId,
|
|
@@ -8820,15 +9395,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
8820
9395
|
if (allConfigs.length === 1) {
|
|
8821
9396
|
const primary = allConfigs[0];
|
|
8822
9397
|
service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
|
|
8823
|
-
const relPath =
|
|
9398
|
+
const relPath = path34.relative(scanPath, primary.sourceFile);
|
|
8824
9399
|
const cfgId = configId(relPath);
|
|
8825
9400
|
if (!graph.hasNode(cfgId)) {
|
|
8826
9401
|
const cfgNode = {
|
|
8827
9402
|
id: cfgId,
|
|
8828
9403
|
type: NodeType20.ConfigNode,
|
|
8829
|
-
name:
|
|
9404
|
+
name: path34.basename(primary.sourceFile),
|
|
8830
9405
|
path: relPath,
|
|
8831
|
-
fileType: isConfigFile(
|
|
9406
|
+
fileType: isConfigFile(path34.basename(primary.sourceFile)).fileType || "config"
|
|
8832
9407
|
};
|
|
8833
9408
|
graph.addNode(cfgId, cfgNode);
|
|
8834
9409
|
nodesAdded++;
|
|
@@ -8868,8 +9443,8 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
8868
9443
|
}
|
|
8869
9444
|
|
|
8870
9445
|
// src/extract/configs.ts
|
|
8871
|
-
import { promises as
|
|
8872
|
-
import
|
|
9446
|
+
import { promises as fs23 } from "fs";
|
|
9447
|
+
import path35 from "path";
|
|
8873
9448
|
import {
|
|
8874
9449
|
EdgeType as EdgeType11,
|
|
8875
9450
|
NodeType as NodeType21,
|
|
@@ -8878,23 +9453,23 @@ import {
|
|
|
8878
9453
|
confidenceForExtracted as confidenceForExtracted8
|
|
8879
9454
|
} from "@neat.is/types";
|
|
8880
9455
|
async function walkConfigFiles(dir, excludeDirs = []) {
|
|
8881
|
-
const excluded = new Set(excludeDirs.map((d) =>
|
|
9456
|
+
const excluded = new Set(excludeDirs.map((d) => path35.resolve(d)));
|
|
8882
9457
|
const out = [];
|
|
8883
|
-
async function
|
|
8884
|
-
const entries = await
|
|
9458
|
+
async function walk10(current) {
|
|
9459
|
+
const entries = await fs23.readdir(current, { withFileTypes: true });
|
|
8885
9460
|
for (const entry of entries) {
|
|
8886
|
-
const full =
|
|
9461
|
+
const full = path35.join(current, entry.name);
|
|
8887
9462
|
if (entry.isDirectory()) {
|
|
8888
9463
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
8889
|
-
if (excluded.has(
|
|
9464
|
+
if (excluded.has(path35.resolve(full))) continue;
|
|
8890
9465
|
if (await isPythonVenvDir(full)) continue;
|
|
8891
|
-
await
|
|
9466
|
+
await walk10(full);
|
|
8892
9467
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
8893
9468
|
out.push(full);
|
|
8894
9469
|
}
|
|
8895
9470
|
}
|
|
8896
9471
|
}
|
|
8897
|
-
await
|
|
9472
|
+
await walk10(dir);
|
|
8898
9473
|
return out;
|
|
8899
9474
|
}
|
|
8900
9475
|
async function addConfigNodes(graph, services, scanPath) {
|
|
@@ -8903,19 +9478,19 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
8903
9478
|
for (const service of services) {
|
|
8904
9479
|
const configFiles = await walkConfigFiles(service.dir, service.excludeDirs);
|
|
8905
9480
|
for (const file of configFiles) {
|
|
8906
|
-
const relPath =
|
|
9481
|
+
const relPath = path35.relative(scanPath, file);
|
|
8907
9482
|
const node = {
|
|
8908
9483
|
id: configId2(relPath),
|
|
8909
9484
|
type: NodeType21.ConfigNode,
|
|
8910
|
-
name:
|
|
9485
|
+
name: path35.basename(file),
|
|
8911
9486
|
path: relPath,
|
|
8912
|
-
fileType: isConfigFile(
|
|
9487
|
+
fileType: isConfigFile(path35.basename(file)).fileType
|
|
8913
9488
|
};
|
|
8914
9489
|
if (!graph.hasNode(node.id)) {
|
|
8915
9490
|
graph.addNode(node.id, node);
|
|
8916
9491
|
nodesAdded++;
|
|
8917
9492
|
}
|
|
8918
|
-
const relToService = toPosix(
|
|
9493
|
+
const relToService = toPosix(path35.relative(service.dir, file));
|
|
8919
9494
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
8920
9495
|
graph,
|
|
8921
9496
|
service.pkg.name,
|
|
@@ -8931,7 +9506,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
8931
9506
|
type: EdgeType11.CONFIGURED_BY,
|
|
8932
9507
|
provenance: Provenance11.EXTRACTED,
|
|
8933
9508
|
confidence: confidenceForExtracted8("structural"),
|
|
8934
|
-
evidence: { file: relPath.split(
|
|
9509
|
+
evidence: { file: relPath.split(path35.sep).join("/") }
|
|
8935
9510
|
};
|
|
8936
9511
|
if (!graph.hasEdge(edge.id)) {
|
|
8937
9512
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -8943,8 +9518,8 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
8943
9518
|
}
|
|
8944
9519
|
|
|
8945
9520
|
// src/extract/proto.ts
|
|
8946
|
-
import { promises as
|
|
8947
|
-
import
|
|
9521
|
+
import { promises as fs24 } from "fs";
|
|
9522
|
+
import path36 from "path";
|
|
8948
9523
|
import {
|
|
8949
9524
|
EdgeType as EdgeType12,
|
|
8950
9525
|
NodeType as NodeType22,
|
|
@@ -8989,23 +9564,23 @@ function grpcMethodsFromProto(content, fqPackage) {
|
|
|
8989
9564
|
return out;
|
|
8990
9565
|
}
|
|
8991
9566
|
async function walkProtoFiles(dir, excludeDirs = []) {
|
|
8992
|
-
const excluded = new Set(excludeDirs.map((d) =>
|
|
9567
|
+
const excluded = new Set(excludeDirs.map((d) => path36.resolve(d)));
|
|
8993
9568
|
const out = [];
|
|
8994
|
-
async function
|
|
8995
|
-
const entries = await
|
|
9569
|
+
async function walk10(current) {
|
|
9570
|
+
const entries = await fs24.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
8996
9571
|
for (const entry of entries) {
|
|
8997
|
-
const full =
|
|
9572
|
+
const full = path36.join(current, entry.name);
|
|
8998
9573
|
if (entry.isDirectory()) {
|
|
8999
9574
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
9000
|
-
if (excluded.has(
|
|
9575
|
+
if (excluded.has(path36.resolve(full))) continue;
|
|
9001
9576
|
if (await isPythonVenvDir(full)) continue;
|
|
9002
|
-
await
|
|
9003
|
-
} else if (entry.isFile() &&
|
|
9577
|
+
await walk10(full);
|
|
9578
|
+
} else if (entry.isFile() && path36.extname(entry.name) === PROTO_EXTENSION) {
|
|
9004
9579
|
out.push(full);
|
|
9005
9580
|
}
|
|
9006
9581
|
}
|
|
9007
9582
|
}
|
|
9008
|
-
await
|
|
9583
|
+
await walk10(dir);
|
|
9009
9584
|
return out;
|
|
9010
9585
|
}
|
|
9011
9586
|
async function addGrpcMethods(graph, services) {
|
|
@@ -9015,10 +9590,10 @@ async function addGrpcMethods(graph, services) {
|
|
|
9015
9590
|
const protoPaths = await walkProtoFiles(service.dir, service.excludeDirs);
|
|
9016
9591
|
for (const protoPath of protoPaths) {
|
|
9017
9592
|
if (isTestPath(protoPath)) continue;
|
|
9018
|
-
const relFile = toPosix(
|
|
9593
|
+
const relFile = toPosix(path36.relative(service.dir, protoPath));
|
|
9019
9594
|
let content;
|
|
9020
9595
|
try {
|
|
9021
|
-
content = await
|
|
9596
|
+
content = await fs24.readFile(protoPath, "utf8");
|
|
9022
9597
|
} catch (err) {
|
|
9023
9598
|
recordExtractionError("proto extraction", protoPath, err);
|
|
9024
9599
|
continue;
|
|
@@ -9081,7 +9656,7 @@ import {
|
|
|
9081
9656
|
} from "@neat.is/types";
|
|
9082
9657
|
|
|
9083
9658
|
// src/extract/calls/http.ts
|
|
9084
|
-
import
|
|
9659
|
+
import path37 from "path";
|
|
9085
9660
|
import Parser6 from "tree-sitter";
|
|
9086
9661
|
import JavaScript4 from "tree-sitter-javascript";
|
|
9087
9662
|
import TypeScript2 from "tree-sitter-typescript";
|
|
@@ -9169,7 +9744,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
9169
9744
|
const seen = /* @__PURE__ */ new Set();
|
|
9170
9745
|
for (const file of files) {
|
|
9171
9746
|
if (isTestPath(file.path)) continue;
|
|
9172
|
-
const parser = parserForExt(
|
|
9747
|
+
const parser = parserForExt(path37.extname(file.path), parserCache);
|
|
9173
9748
|
let sites;
|
|
9174
9749
|
try {
|
|
9175
9750
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -9178,7 +9753,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
9178
9753
|
continue;
|
|
9179
9754
|
}
|
|
9180
9755
|
if (sites.length === 0) continue;
|
|
9181
|
-
const relFile = toPosix(
|
|
9756
|
+
const relFile = toPosix(path37.relative(service.dir, file.path));
|
|
9182
9757
|
for (const site of sites) {
|
|
9183
9758
|
const targetId = hostToNodeId.get(site.host);
|
|
9184
9759
|
if (!targetId || targetId === service.node.id) continue;
|
|
@@ -9231,7 +9806,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
9231
9806
|
}
|
|
9232
9807
|
|
|
9233
9808
|
// src/extract/calls/route-match.ts
|
|
9234
|
-
import
|
|
9809
|
+
import path38 from "path";
|
|
9235
9810
|
import Parser7 from "tree-sitter";
|
|
9236
9811
|
import JavaScript5 from "tree-sitter-javascript";
|
|
9237
9812
|
import {
|
|
@@ -9437,7 +10012,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
9437
10012
|
const seen = /* @__PURE__ */ new Set();
|
|
9438
10013
|
for (const file of files) {
|
|
9439
10014
|
if (isTestPath(file.path)) continue;
|
|
9440
|
-
if (!JS_CLIENT_EXTENSIONS.has(
|
|
10015
|
+
if (!JS_CLIENT_EXTENSIONS.has(path38.extname(file.path))) continue;
|
|
9441
10016
|
let sites;
|
|
9442
10017
|
try {
|
|
9443
10018
|
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
@@ -9446,7 +10021,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
9446
10021
|
continue;
|
|
9447
10022
|
}
|
|
9448
10023
|
if (sites.length === 0) continue;
|
|
9449
|
-
const relFile = toPosix(
|
|
10024
|
+
const relFile = toPosix(path38.relative(service.dir, file.path));
|
|
9450
10025
|
for (const site of sites) {
|
|
9451
10026
|
const serverServiceId = hostToNodeId.get(site.host);
|
|
9452
10027
|
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
@@ -9506,7 +10081,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
9506
10081
|
}
|
|
9507
10082
|
|
|
9508
10083
|
// src/extract/calls/kafka.ts
|
|
9509
|
-
import
|
|
10084
|
+
import path39 from "path";
|
|
9510
10085
|
import { infraId as infraId2 } from "@neat.is/types";
|
|
9511
10086
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
9512
10087
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -9594,13 +10169,13 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
9594
10169
|
// call sites — verified-call-site tier (ADR-066).
|
|
9595
10170
|
confidenceKind: "verified-call-site",
|
|
9596
10171
|
evidence: {
|
|
9597
|
-
file:
|
|
10172
|
+
file: path39.relative(serviceDir, file.path),
|
|
9598
10173
|
line,
|
|
9599
10174
|
snippet: snippet(file.content, line)
|
|
9600
10175
|
}
|
|
9601
10176
|
});
|
|
9602
10177
|
};
|
|
9603
|
-
if (
|
|
10178
|
+
if (path39.extname(file.path) === ".go") {
|
|
9604
10179
|
goSaramaEndpoints(file.content, make);
|
|
9605
10180
|
} else {
|
|
9606
10181
|
for (const { topic } of findAll(PRODUCER_TOPIC_RE, file.content)) make(topic, "PUBLISHES_TO");
|
|
@@ -9610,7 +10185,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
9610
10185
|
}
|
|
9611
10186
|
|
|
9612
10187
|
// src/extract/calls/redis.ts
|
|
9613
|
-
import
|
|
10188
|
+
import path40 from "path";
|
|
9614
10189
|
import { infraId as infraId3 } from "@neat.is/types";
|
|
9615
10190
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
9616
10191
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -9633,7 +10208,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
9633
10208
|
// support tier (ADR-066).
|
|
9634
10209
|
confidenceKind: "url-with-structural-support",
|
|
9635
10210
|
evidence: {
|
|
9636
|
-
file:
|
|
10211
|
+
file: path40.relative(serviceDir, file.path),
|
|
9637
10212
|
line,
|
|
9638
10213
|
snippet: snippet(file.content, line)
|
|
9639
10214
|
}
|
|
@@ -9643,7 +10218,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
9643
10218
|
}
|
|
9644
10219
|
|
|
9645
10220
|
// src/extract/calls/aws.ts
|
|
9646
|
-
import
|
|
10221
|
+
import path41 from "path";
|
|
9647
10222
|
import { infraId as infraId4 } from "@neat.is/types";
|
|
9648
10223
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
9649
10224
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -9677,7 +10252,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
9677
10252
|
// (ADR-066).
|
|
9678
10253
|
confidenceKind: "verified-call-site",
|
|
9679
10254
|
evidence: {
|
|
9680
|
-
file:
|
|
10255
|
+
file: path41.relative(serviceDir, file.path),
|
|
9681
10256
|
line,
|
|
9682
10257
|
snippet: snippet(file.content, line)
|
|
9683
10258
|
}
|
|
@@ -9701,7 +10276,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
9701
10276
|
}
|
|
9702
10277
|
|
|
9703
10278
|
// src/extract/calls/grpc.ts
|
|
9704
|
-
import
|
|
10279
|
+
import path42 from "path";
|
|
9705
10280
|
import { infraId as infraId5 } from "@neat.is/types";
|
|
9706
10281
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
9707
10282
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
@@ -9760,7 +10335,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
9760
10335
|
// tier (ADR-066).
|
|
9761
10336
|
confidenceKind: "verified-call-site",
|
|
9762
10337
|
evidence: {
|
|
9763
|
-
file:
|
|
10338
|
+
file: path42.relative(serviceDir, file.path),
|
|
9764
10339
|
line,
|
|
9765
10340
|
snippet: snippet(file.content, line)
|
|
9766
10341
|
}
|
|
@@ -9770,7 +10345,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
9770
10345
|
}
|
|
9771
10346
|
|
|
9772
10347
|
// src/extract/calls/supabase.ts
|
|
9773
|
-
import
|
|
10348
|
+
import path43 from "path";
|
|
9774
10349
|
import { infraId as infraId6 } from "@neat.is/types";
|
|
9775
10350
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
9776
10351
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
@@ -9829,7 +10404,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
9829
10404
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
9830
10405
|
confidenceKind: "verified-call-site",
|
|
9831
10406
|
evidence: {
|
|
9832
|
-
file:
|
|
10407
|
+
file: path43.relative(serviceDir, file.path),
|
|
9833
10408
|
line,
|
|
9834
10409
|
snippet: snippet(file.content, line)
|
|
9835
10410
|
}
|
|
@@ -9856,7 +10431,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
9856
10431
|
edgeType: "CALLS",
|
|
9857
10432
|
confidenceKind: "verified-call-site",
|
|
9858
10433
|
evidence: {
|
|
9859
|
-
file:
|
|
10434
|
+
file: path43.relative(serviceDir, file.path),
|
|
9860
10435
|
line,
|
|
9861
10436
|
snippet: snippet(file.content, line)
|
|
9862
10437
|
}
|
|
@@ -9867,7 +10442,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
9867
10442
|
}
|
|
9868
10443
|
|
|
9869
10444
|
// src/extract/calls/firestore.ts
|
|
9870
|
-
import
|
|
10445
|
+
import path44 from "path";
|
|
9871
10446
|
import Parser8 from "tree-sitter";
|
|
9872
10447
|
import JavaScript6 from "tree-sitter-javascript";
|
|
9873
10448
|
import { infraId as infraId7 } from "@neat.is/types";
|
|
@@ -9906,7 +10481,7 @@ function isFirestoreClientFactory(node) {
|
|
|
9906
10481
|
}
|
|
9907
10482
|
function firestoreClientVars(root) {
|
|
9908
10483
|
const vars = /* @__PURE__ */ new Set();
|
|
9909
|
-
const
|
|
10484
|
+
const walk10 = (node) => {
|
|
9910
10485
|
if (node.type === "variable_declarator") {
|
|
9911
10486
|
const name = node.childForFieldName("name");
|
|
9912
10487
|
let value = node.childForFieldName("value");
|
|
@@ -9915,9 +10490,9 @@ function firestoreClientVars(root) {
|
|
|
9915
10490
|
vars.add(name.text);
|
|
9916
10491
|
}
|
|
9917
10492
|
}
|
|
9918
|
-
for (const c of namedChildren(node))
|
|
10493
|
+
for (const c of namedChildren(node)) walk10(c);
|
|
9919
10494
|
};
|
|
9920
|
-
|
|
10495
|
+
walk10(root);
|
|
9921
10496
|
return vars;
|
|
9922
10497
|
}
|
|
9923
10498
|
function isClientExpr(node, clientVars) {
|
|
@@ -10038,7 +10613,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10038
10613
|
const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
|
|
10039
10614
|
if (!hasClient && !hasAdmin) return [];
|
|
10040
10615
|
const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
10041
|
-
const tree = parseSource3(parserForExt2(
|
|
10616
|
+
const tree = parseSource3(parserForExt2(path44.extname(file.path)), file.content);
|
|
10042
10617
|
const clientVars = firestoreClientVars(tree.rootNode);
|
|
10043
10618
|
const collLine = /* @__PURE__ */ new Map();
|
|
10044
10619
|
const writes = /* @__PURE__ */ new Map();
|
|
@@ -10072,7 +10647,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10072
10647
|
}
|
|
10073
10648
|
s.add(field);
|
|
10074
10649
|
};
|
|
10075
|
-
const
|
|
10650
|
+
const walk10 = (node) => {
|
|
10076
10651
|
if (node.type === "call_expression") {
|
|
10077
10652
|
const fn = node.childForFieldName("function");
|
|
10078
10653
|
const line = node.startPosition.row + 1;
|
|
@@ -10112,9 +10687,9 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10112
10687
|
}
|
|
10113
10688
|
}
|
|
10114
10689
|
}
|
|
10115
|
-
for (const c of namedChildren(node))
|
|
10690
|
+
for (const c of namedChildren(node)) walk10(c);
|
|
10116
10691
|
};
|
|
10117
|
-
|
|
10692
|
+
walk10(tree.rootNode);
|
|
10118
10693
|
const out = [];
|
|
10119
10694
|
for (const [collPath, line] of collLine) {
|
|
10120
10695
|
const byField = writes.get(collPath);
|
|
@@ -10142,7 +10717,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10142
10717
|
...columnSet.size > 0 ? { columns: [...columnSet] } : {},
|
|
10143
10718
|
...sdkWrites ? { sdkWrites } : {},
|
|
10144
10719
|
evidence: {
|
|
10145
|
-
file:
|
|
10720
|
+
file: path44.relative(serviceDir, file.path),
|
|
10146
10721
|
line,
|
|
10147
10722
|
snippet: snippet(file.content, line)
|
|
10148
10723
|
}
|
|
@@ -10152,7 +10727,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10152
10727
|
}
|
|
10153
10728
|
|
|
10154
10729
|
// src/extract/calls/mongoose.ts
|
|
10155
|
-
import
|
|
10730
|
+
import path45 from "path";
|
|
10156
10731
|
import { infraId as infraId8 } from "@neat.is/types";
|
|
10157
10732
|
var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
|
|
10158
10733
|
var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
|
|
@@ -10303,7 +10878,7 @@ function endpoint(r, file, serviceDir, matchText) {
|
|
|
10303
10878
|
kind: r.kind,
|
|
10304
10879
|
edgeType: "CALLS",
|
|
10305
10880
|
confidenceKind: "verified-call-site",
|
|
10306
|
-
evidence: { file:
|
|
10881
|
+
evidence: { file: path45.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
10307
10882
|
};
|
|
10308
10883
|
}
|
|
10309
10884
|
function mongooseEndpointsFromFile(file, serviceDir) {
|
|
@@ -10393,7 +10968,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
10393
10968
|
const registry = /* @__PURE__ */ new Map();
|
|
10394
10969
|
for (const f of mongooseFiles) {
|
|
10395
10970
|
const fx = fileExportsOf(f.content, pluralizeOn);
|
|
10396
|
-
if (fx) registry.set(toPosix(
|
|
10971
|
+
if (fx) registry.set(toPosix(path45.relative(serviceDir, f.path)), fx);
|
|
10397
10972
|
}
|
|
10398
10973
|
if (registry.size === 0) return [];
|
|
10399
10974
|
const out = [];
|
|
@@ -10404,7 +10979,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
10404
10979
|
const directColl = /* @__PURE__ */ new Map();
|
|
10405
10980
|
const nsExports = /* @__PURE__ */ new Map();
|
|
10406
10981
|
for (const b of bindings) {
|
|
10407
|
-
const resolvedRel = await resolveJsImport(b.specifier,
|
|
10982
|
+
const resolvedRel = await resolveJsImport(b.specifier, path45.dirname(f.path), serviceDir, null);
|
|
10408
10983
|
if (!resolvedRel) continue;
|
|
10409
10984
|
const fx = registry.get(resolvedRel);
|
|
10410
10985
|
if (!fx) continue;
|
|
@@ -10447,7 +11022,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
10447
11022
|
}
|
|
10448
11023
|
|
|
10449
11024
|
// src/extract/calls/sqlalchemy.ts
|
|
10450
|
-
import
|
|
11025
|
+
import path46 from "path";
|
|
10451
11026
|
import Parser9 from "tree-sitter";
|
|
10452
11027
|
import Python5 from "tree-sitter-python";
|
|
10453
11028
|
import { infraId as infraId9 } from "@neat.is/types";
|
|
@@ -10586,7 +11161,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
|
|
|
10586
11161
|
childTable,
|
|
10587
11162
|
parentTable,
|
|
10588
11163
|
evidence: {
|
|
10589
|
-
file:
|
|
11164
|
+
file: path46.relative(serviceDir, file.path),
|
|
10590
11165
|
line,
|
|
10591
11166
|
snippet: snippet(file.content, line)
|
|
10592
11167
|
}
|
|
@@ -10611,7 +11186,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
|
10611
11186
|
confidenceKind: "verified-call-site",
|
|
10612
11187
|
...columns && columns.length > 0 ? { columns } : {},
|
|
10613
11188
|
evidence: {
|
|
10614
|
-
file:
|
|
11189
|
+
file: path46.relative(serviceDir, file.path),
|
|
10615
11190
|
line,
|
|
10616
11191
|
snippet: snippet(file.content, line)
|
|
10617
11192
|
}
|
|
@@ -10718,7 +11293,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
10718
11293
|
edgeType: "CALLS",
|
|
10719
11294
|
confidenceKind: "verified-call-site",
|
|
10720
11295
|
evidence: {
|
|
10721
|
-
file:
|
|
11296
|
+
file: path46.relative(serviceDir, file.path),
|
|
10722
11297
|
line,
|
|
10723
11298
|
snippet: snippet(file.content, line)
|
|
10724
11299
|
}
|
|
@@ -10729,7 +11304,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
10729
11304
|
}
|
|
10730
11305
|
|
|
10731
11306
|
// src/extract/calls/django-orm.ts
|
|
10732
|
-
import
|
|
11307
|
+
import path47 from "path";
|
|
10733
11308
|
import Parser10 from "tree-sitter";
|
|
10734
11309
|
import Python6 from "tree-sitter-python";
|
|
10735
11310
|
import { infraId as infraId10 } from "@neat.is/types";
|
|
@@ -10799,7 +11374,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
10799
11374
|
const tree = parseSource7(makePyParser4(), file.content);
|
|
10800
11375
|
const out = [];
|
|
10801
11376
|
const seen = /* @__PURE__ */ new Set();
|
|
10802
|
-
const defaultAppLabel =
|
|
11377
|
+
const defaultAppLabel = path47.basename(path47.dirname(file.path));
|
|
10803
11378
|
walk4(tree.rootNode, (node) => {
|
|
10804
11379
|
if (node.type !== "class_definition") return;
|
|
10805
11380
|
if (!extendsDjangoModel(node)) return;
|
|
@@ -10817,14 +11392,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
10817
11392
|
kind: "sql-table",
|
|
10818
11393
|
edgeType: "CALLS",
|
|
10819
11394
|
confidenceKind: "verified-call-site",
|
|
10820
|
-
evidence: { file:
|
|
11395
|
+
evidence: { file: path47.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
10821
11396
|
});
|
|
10822
11397
|
});
|
|
10823
11398
|
return out;
|
|
10824
11399
|
}
|
|
10825
11400
|
|
|
10826
11401
|
// src/extract/calls/drizzle.ts
|
|
10827
|
-
import
|
|
11402
|
+
import path48 from "path";
|
|
10828
11403
|
import Parser11 from "tree-sitter";
|
|
10829
11404
|
import JavaScript7 from "tree-sitter-javascript";
|
|
10830
11405
|
import { infraId as infraId11 } from "@neat.is/types";
|
|
@@ -10902,10 +11477,10 @@ function columnsFromObject(obj) {
|
|
|
10902
11477
|
}
|
|
10903
11478
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
10904
11479
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10905
|
-
const tree = parseSource3(parserForExt3(
|
|
11480
|
+
const tree = parseSource3(parserForExt3(path48.extname(file.path)), file.content);
|
|
10906
11481
|
const out = [];
|
|
10907
11482
|
const seen = /* @__PURE__ */ new Set();
|
|
10908
|
-
const
|
|
11483
|
+
const walk10 = (node) => {
|
|
10909
11484
|
if (node.type === "call_expression") {
|
|
10910
11485
|
const fn = node.childForFieldName("function");
|
|
10911
11486
|
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
@@ -10925,7 +11500,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
10925
11500
|
confidenceKind: "structural",
|
|
10926
11501
|
columns,
|
|
10927
11502
|
evidence: {
|
|
10928
|
-
file:
|
|
11503
|
+
file: path48.relative(serviceDir, file.path),
|
|
10929
11504
|
line,
|
|
10930
11505
|
snippet: snippet(file.content, line)
|
|
10931
11506
|
}
|
|
@@ -10933,9 +11508,9 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
10933
11508
|
}
|
|
10934
11509
|
}
|
|
10935
11510
|
}
|
|
10936
|
-
for (const c of namedChildren4(node))
|
|
11511
|
+
for (const c of namedChildren4(node)) walk10(c);
|
|
10937
11512
|
};
|
|
10938
|
-
|
|
11513
|
+
walk10(tree.rootNode);
|
|
10939
11514
|
return out;
|
|
10940
11515
|
}
|
|
10941
11516
|
function enclosingVarName(call) {
|
|
@@ -10957,7 +11532,7 @@ function enclosingVarName(call) {
|
|
|
10957
11532
|
function collectDrizzleTables(root) {
|
|
10958
11533
|
const tables = [];
|
|
10959
11534
|
const varToTable = /* @__PURE__ */ new Map();
|
|
10960
|
-
const
|
|
11535
|
+
const walk10 = (node) => {
|
|
10961
11536
|
if (node.type === "call_expression") {
|
|
10962
11537
|
const fn = node.childForFieldName("function");
|
|
10963
11538
|
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
@@ -10972,9 +11547,9 @@ function collectDrizzleTables(root) {
|
|
|
10972
11547
|
}
|
|
10973
11548
|
}
|
|
10974
11549
|
}
|
|
10975
|
-
for (const c of namedChildren4(node))
|
|
11550
|
+
for (const c of namedChildren4(node)) walk10(c);
|
|
10976
11551
|
};
|
|
10977
|
-
|
|
11552
|
+
walk10(root);
|
|
10978
11553
|
return { tables, varToTable };
|
|
10979
11554
|
}
|
|
10980
11555
|
function referencesTargetVar(call) {
|
|
@@ -10991,13 +11566,13 @@ function referencesTargetVar(call) {
|
|
|
10991
11566
|
}
|
|
10992
11567
|
function drizzleForeignKeys(file, serviceDir) {
|
|
10993
11568
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10994
|
-
const tree = parseSource3(parserForExt3(
|
|
11569
|
+
const tree = parseSource3(parserForExt3(path48.extname(file.path)), file.content);
|
|
10995
11570
|
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
10996
11571
|
const out = [];
|
|
10997
11572
|
const seen = /* @__PURE__ */ new Set();
|
|
10998
11573
|
for (const table of tables) {
|
|
10999
11574
|
if (!table.object) continue;
|
|
11000
|
-
const
|
|
11575
|
+
const walk10 = (node) => {
|
|
11001
11576
|
if (node.type === "call_expression") {
|
|
11002
11577
|
const targetVar = referencesTargetVar(node);
|
|
11003
11578
|
const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
|
|
@@ -11010,7 +11585,7 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
11010
11585
|
childTable: table.tableName,
|
|
11011
11586
|
parentTable,
|
|
11012
11587
|
evidence: {
|
|
11013
|
-
file:
|
|
11588
|
+
file: path48.relative(serviceDir, file.path),
|
|
11014
11589
|
line,
|
|
11015
11590
|
snippet: snippet(file.content, line)
|
|
11016
11591
|
}
|
|
@@ -11018,15 +11593,15 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
11018
11593
|
}
|
|
11019
11594
|
}
|
|
11020
11595
|
}
|
|
11021
|
-
for (const c of namedChildren4(node))
|
|
11596
|
+
for (const c of namedChildren4(node)) walk10(c);
|
|
11022
11597
|
};
|
|
11023
|
-
|
|
11598
|
+
walk10(table.object);
|
|
11024
11599
|
}
|
|
11025
11600
|
return out;
|
|
11026
11601
|
}
|
|
11027
11602
|
|
|
11028
11603
|
// src/extract/calls/prisma.ts
|
|
11029
|
-
import
|
|
11604
|
+
import path49 from "path";
|
|
11030
11605
|
import { infraId as infraId12 } from "@neat.is/types";
|
|
11031
11606
|
var SCALAR_TYPES = /* @__PURE__ */ new Set([
|
|
11032
11607
|
"Int",
|
|
@@ -11082,7 +11657,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
11082
11657
|
confidenceKind: "structural",
|
|
11083
11658
|
columns: b.columns,
|
|
11084
11659
|
evidence: {
|
|
11085
|
-
file:
|
|
11660
|
+
file: path49.relative(serviceDir, file.path),
|
|
11086
11661
|
line: b.startLine,
|
|
11087
11662
|
snippet: snippet(content, b.startLine)
|
|
11088
11663
|
}
|
|
@@ -11143,7 +11718,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
11143
11718
|
}
|
|
11144
11719
|
async function prismaColumnEndpoints(serviceDir) {
|
|
11145
11720
|
const schemaPath = await findFirst(serviceDir, [
|
|
11146
|
-
|
|
11721
|
+
path49.join("prisma", "schema.prisma"),
|
|
11147
11722
|
"schema.prisma"
|
|
11148
11723
|
]);
|
|
11149
11724
|
if (!schemaPath) return [];
|
|
@@ -11218,7 +11793,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
11218
11793
|
childTable: current.table,
|
|
11219
11794
|
parentTable,
|
|
11220
11795
|
evidence: {
|
|
11221
|
-
file:
|
|
11796
|
+
file: path49.relative(serviceDir, file.path),
|
|
11222
11797
|
line: lineNo,
|
|
11223
11798
|
snippet: snippet(content, lineNo)
|
|
11224
11799
|
}
|
|
@@ -11233,7 +11808,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
11233
11808
|
}
|
|
11234
11809
|
async function prismaForeignKeys(serviceDir) {
|
|
11235
11810
|
const schemaPath = await findFirst(serviceDir, [
|
|
11236
|
-
|
|
11811
|
+
path49.join("prisma", "schema.prisma"),
|
|
11237
11812
|
"schema.prisma"
|
|
11238
11813
|
]);
|
|
11239
11814
|
if (!schemaPath) return [];
|
|
@@ -11243,7 +11818,7 @@ async function prismaForeignKeys(serviceDir) {
|
|
|
11243
11818
|
}
|
|
11244
11819
|
|
|
11245
11820
|
// src/extract/calls/activerecord.ts
|
|
11246
|
-
import
|
|
11821
|
+
import path50 from "path";
|
|
11247
11822
|
import Parser12 from "tree-sitter";
|
|
11248
11823
|
import Ruby3 from "tree-sitter-ruby";
|
|
11249
11824
|
import { infraId as infraId13 } from "@neat.is/types";
|
|
@@ -11451,7 +12026,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
|
|
|
11451
12026
|
confidenceKind: "structural",
|
|
11452
12027
|
...table.columns.length > 0 ? { columns: table.columns } : {},
|
|
11453
12028
|
evidence: {
|
|
11454
|
-
file:
|
|
12029
|
+
file: path50.relative(serviceDir, file.path),
|
|
11455
12030
|
line: table.line,
|
|
11456
12031
|
snippet: snippet(file.content, table.line)
|
|
11457
12032
|
}
|
|
@@ -11473,7 +12048,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
|
|
|
11473
12048
|
childTable,
|
|
11474
12049
|
parentTable,
|
|
11475
12050
|
evidence: {
|
|
11476
|
-
file:
|
|
12051
|
+
file: path50.relative(serviceDir, file.path),
|
|
11477
12052
|
line,
|
|
11478
12053
|
snippet: snippet(file.content, line)
|
|
11479
12054
|
}
|
|
@@ -11556,7 +12131,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
|
|
|
11556
12131
|
edgeType: "CALLS",
|
|
11557
12132
|
confidenceKind: "verified-call-site",
|
|
11558
12133
|
evidence: {
|
|
11559
|
-
file:
|
|
12134
|
+
file: path50.relative(serviceDir, file.path),
|
|
11560
12135
|
line,
|
|
11561
12136
|
snippet: snippet(file.content, line)
|
|
11562
12137
|
}
|
|
@@ -11593,7 +12168,7 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
11593
12168
|
childTable,
|
|
11594
12169
|
parentTable,
|
|
11595
12170
|
evidence: {
|
|
11596
|
-
file:
|
|
12171
|
+
file: path50.relative(serviceDir, file.path),
|
|
11597
12172
|
line,
|
|
11598
12173
|
snippet: snippet(file.content, line)
|
|
11599
12174
|
}
|
|
@@ -11604,7 +12179,7 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
11604
12179
|
}
|
|
11605
12180
|
|
|
11606
12181
|
// src/extract/calls/eloquent.ts
|
|
11607
|
-
import
|
|
12182
|
+
import path51 from "path";
|
|
11608
12183
|
import Parser13 from "tree-sitter";
|
|
11609
12184
|
import Php3 from "tree-sitter-php";
|
|
11610
12185
|
import { infraId as infraId14 } from "@neat.is/types";
|
|
@@ -11861,7 +12436,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
|
|
|
11861
12436
|
confidenceKind: "structural",
|
|
11862
12437
|
...columns.length > 0 ? { columns } : {},
|
|
11863
12438
|
evidence: {
|
|
11864
|
-
file:
|
|
12439
|
+
file: path51.relative(serviceDir, file.path),
|
|
11865
12440
|
line: bp.line,
|
|
11866
12441
|
snippet: snippet(file.content, bp.line)
|
|
11867
12442
|
}
|
|
@@ -11883,7 +12458,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
|
|
|
11883
12458
|
childTable,
|
|
11884
12459
|
parentTable,
|
|
11885
12460
|
evidence: {
|
|
11886
|
-
file:
|
|
12461
|
+
file: path51.relative(serviceDir, file.path),
|
|
11887
12462
|
line,
|
|
11888
12463
|
snippet: snippet(file.content, line)
|
|
11889
12464
|
}
|
|
@@ -12003,7 +12578,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
|
|
|
12003
12578
|
edgeType: "CALLS",
|
|
12004
12579
|
confidenceKind: "verified-call-site",
|
|
12005
12580
|
evidence: {
|
|
12006
|
-
file:
|
|
12581
|
+
file: path51.relative(serviceDir, file.path),
|
|
12007
12582
|
line,
|
|
12008
12583
|
snippet: snippet(file.content, line)
|
|
12009
12584
|
}
|
|
@@ -12039,7 +12614,7 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
12039
12614
|
childTable,
|
|
12040
12615
|
parentTable,
|
|
12041
12616
|
evidence: {
|
|
12042
|
-
file:
|
|
12617
|
+
file: path51.relative(serviceDir, file.path),
|
|
12043
12618
|
line,
|
|
12044
12619
|
snippet: snippet(file.content, line)
|
|
12045
12620
|
}
|
|
@@ -12050,7 +12625,7 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
12050
12625
|
}
|
|
12051
12626
|
|
|
12052
12627
|
// src/extract/calls/go.ts
|
|
12053
|
-
import
|
|
12628
|
+
import path52 from "path";
|
|
12054
12629
|
import Parser14 from "tree-sitter";
|
|
12055
12630
|
import Go4 from "tree-sitter-go";
|
|
12056
12631
|
import { infraId as infraId15 } from "@neat.is/types";
|
|
@@ -12124,7 +12699,7 @@ function firstStringLiteralArg(argsNode) {
|
|
|
12124
12699
|
return null;
|
|
12125
12700
|
}
|
|
12126
12701
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
12127
|
-
if (
|
|
12702
|
+
if (path52.extname(file.path) !== ".go") return [];
|
|
12128
12703
|
if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
|
|
12129
12704
|
const tree = parseSource10(makeGoParser3(), file.content);
|
|
12130
12705
|
const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
|
|
@@ -12153,7 +12728,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
12153
12728
|
confidenceKind: "verified-call-site",
|
|
12154
12729
|
...columns.length > 0 ? { columns } : {},
|
|
12155
12730
|
evidence: {
|
|
12156
|
-
file: toPosix(
|
|
12731
|
+
file: toPosix(path52.relative(serviceDir, file.path)),
|
|
12157
12732
|
line,
|
|
12158
12733
|
snippet: snippet(file.content, line)
|
|
12159
12734
|
}
|
|
@@ -12163,7 +12738,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
12163
12738
|
}
|
|
12164
12739
|
|
|
12165
12740
|
// src/extract/calls/gorm.ts
|
|
12166
|
-
import
|
|
12741
|
+
import path53 from "path";
|
|
12167
12742
|
import Parser15 from "tree-sitter";
|
|
12168
12743
|
import Go5 from "tree-sitter-go";
|
|
12169
12744
|
import { infraId as infraId16 } from "@neat.is/types";
|
|
@@ -12596,7 +13171,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
12596
13171
|
seen.delete(struct.name);
|
|
12597
13172
|
}
|
|
12598
13173
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
12599
|
-
if (
|
|
13174
|
+
if (path53.extname(file.path) !== ".go") return [];
|
|
12600
13175
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
12601
13176
|
const tree = parseSource11(makeGoParser4(), file.content);
|
|
12602
13177
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -12618,7 +13193,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
12618
13193
|
confidenceKind: "structural",
|
|
12619
13194
|
...columns.length > 0 ? { columns } : {},
|
|
12620
13195
|
evidence: {
|
|
12621
|
-
file: toPosix(
|
|
13196
|
+
file: toPosix(path53.relative(serviceDir, file.path)),
|
|
12622
13197
|
line: struct.line,
|
|
12623
13198
|
snippet: snippet(file.content, struct.line)
|
|
12624
13199
|
}
|
|
@@ -12627,7 +13202,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
12627
13202
|
return out;
|
|
12628
13203
|
}
|
|
12629
13204
|
function gormForeignKeys(file, serviceDir) {
|
|
12630
|
-
if (
|
|
13205
|
+
if (path53.extname(file.path) !== ".go") return [];
|
|
12631
13206
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
12632
13207
|
const tree = parseSource11(makeGoParser4(), file.content);
|
|
12633
13208
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -12642,7 +13217,7 @@ function gormForeignKeys(file, serviceDir) {
|
|
|
12642
13217
|
childTable,
|
|
12643
13218
|
parentTable,
|
|
12644
13219
|
evidence: {
|
|
12645
|
-
file: toPosix(
|
|
13220
|
+
file: toPosix(path53.relative(serviceDir, file.path)),
|
|
12646
13221
|
line,
|
|
12647
13222
|
snippet: snippet(file.content, line)
|
|
12648
13223
|
}
|
|
@@ -12677,6 +13252,121 @@ function gormForeignKeys(file, serviceDir) {
|
|
|
12677
13252
|
return out;
|
|
12678
13253
|
}
|
|
12679
13254
|
|
|
13255
|
+
// src/extract/calls/efcore.ts
|
|
13256
|
+
import path54 from "path";
|
|
13257
|
+
import Parser16 from "tree-sitter";
|
|
13258
|
+
import CSharp2 from "tree-sitter-c-sharp";
|
|
13259
|
+
import { infraId as infraId17 } from "@neat.is/types";
|
|
13260
|
+
var EFCORE_GATE = /Microsoft\.EntityFrameworkCore|DataAnnotations\.Schema|\bDbContext\b|\bDbSet\s*</;
|
|
13261
|
+
var PARSE_CHUNK12 = 16384;
|
|
13262
|
+
function makeCsParser() {
|
|
13263
|
+
const p = new Parser16();
|
|
13264
|
+
p.setLanguage(CSharp2);
|
|
13265
|
+
return p;
|
|
13266
|
+
}
|
|
13267
|
+
function parseSource12(parser, source) {
|
|
13268
|
+
return parser.parse(
|
|
13269
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK12)
|
|
13270
|
+
);
|
|
13271
|
+
}
|
|
13272
|
+
function walk9(node, visit) {
|
|
13273
|
+
visit(node);
|
|
13274
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
13275
|
+
const c = node.namedChild(i);
|
|
13276
|
+
if (c) walk9(c, visit);
|
|
13277
|
+
}
|
|
13278
|
+
}
|
|
13279
|
+
function firstChildOfType(node, type) {
|
|
13280
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
13281
|
+
const c = node.namedChild(i);
|
|
13282
|
+
if (c?.type === type) return c;
|
|
13283
|
+
}
|
|
13284
|
+
return null;
|
|
13285
|
+
}
|
|
13286
|
+
function csStringLiteral(node) {
|
|
13287
|
+
if (node.type === "string_literal") {
|
|
13288
|
+
let out = "";
|
|
13289
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
13290
|
+
const c = node.namedChild(i);
|
|
13291
|
+
if (c?.type === "string_literal_content") out += c.text;
|
|
13292
|
+
}
|
|
13293
|
+
return out;
|
|
13294
|
+
}
|
|
13295
|
+
if (node.type === "verbatim_string_literal") {
|
|
13296
|
+
const t = node.text;
|
|
13297
|
+
return t.length >= 3 ? t.slice(2, -1).replace(/""/g, '"') : "";
|
|
13298
|
+
}
|
|
13299
|
+
return null;
|
|
13300
|
+
}
|
|
13301
|
+
function attributeName(attr) {
|
|
13302
|
+
const nameNode = attr.childForFieldName("name") ?? attr.namedChild(0);
|
|
13303
|
+
if (!nameNode) return null;
|
|
13304
|
+
const text = nameNode.text;
|
|
13305
|
+
const base = text.includes(".") ? text.slice(text.lastIndexOf(".") + 1) : text;
|
|
13306
|
+
return base.endsWith("Attribute") ? base.slice(0, -"Attribute".length) : base;
|
|
13307
|
+
}
|
|
13308
|
+
function tableFromAttribute(attr) {
|
|
13309
|
+
if (attributeName(attr) !== "Table") return null;
|
|
13310
|
+
const args = attr.childForFieldName("arguments") ?? firstChildOfType(attr, "attribute_argument_list");
|
|
13311
|
+
if (!args) return null;
|
|
13312
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
13313
|
+
const arg = args.namedChild(i);
|
|
13314
|
+
if (arg?.type !== "attribute_argument") continue;
|
|
13315
|
+
const first = arg.namedChild(0);
|
|
13316
|
+
if (!first) continue;
|
|
13317
|
+
const value = csStringLiteral(first);
|
|
13318
|
+
if (value !== null) return value;
|
|
13319
|
+
return null;
|
|
13320
|
+
}
|
|
13321
|
+
return null;
|
|
13322
|
+
}
|
|
13323
|
+
function tableFromToTable(call) {
|
|
13324
|
+
const fn = call.childForFieldName("function");
|
|
13325
|
+
if (fn?.type !== "member_access_expression") return null;
|
|
13326
|
+
const method = fn.childForFieldName("name") ?? fn.namedChild(fn.namedChildCount - 1);
|
|
13327
|
+
if (method?.text !== "ToTable") return null;
|
|
13328
|
+
const args = call.childForFieldName("arguments");
|
|
13329
|
+
const firstArg2 = args?.namedChild(0);
|
|
13330
|
+
if (firstArg2?.type !== "argument") return null;
|
|
13331
|
+
const value = firstArg2.namedChild(0);
|
|
13332
|
+
return value ? csStringLiteral(value) : null;
|
|
13333
|
+
}
|
|
13334
|
+
function efcoreEndpointsFromFile(file, serviceDir) {
|
|
13335
|
+
if (path54.extname(file.path) !== ".cs") return [];
|
|
13336
|
+
if (!EFCORE_GATE.test(file.content)) return [];
|
|
13337
|
+
const tree = parseSource12(makeCsParser(), file.content);
|
|
13338
|
+
const out = [];
|
|
13339
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13340
|
+
const push = (name, line) => {
|
|
13341
|
+
if (!name || seen.has(name)) return;
|
|
13342
|
+
seen.add(name);
|
|
13343
|
+
out.push({
|
|
13344
|
+
infraId: infraId17("sql-table", name),
|
|
13345
|
+
name,
|
|
13346
|
+
kind: "sql-table",
|
|
13347
|
+
edgeType: "CALLS",
|
|
13348
|
+
confidenceKind: "structural",
|
|
13349
|
+
evidence: {
|
|
13350
|
+
file: toPosix(path54.relative(serviceDir, file.path)),
|
|
13351
|
+
line,
|
|
13352
|
+
snippet: snippet(file.content, line)
|
|
13353
|
+
}
|
|
13354
|
+
});
|
|
13355
|
+
};
|
|
13356
|
+
walk9(tree.rootNode, (node) => {
|
|
13357
|
+
if (node.type === "attribute") {
|
|
13358
|
+
const table = tableFromAttribute(node);
|
|
13359
|
+
if (table) push(table, node.startPosition.row + 1);
|
|
13360
|
+
return;
|
|
13361
|
+
}
|
|
13362
|
+
if (node.type === "invocation_expression") {
|
|
13363
|
+
const table = tableFromToTable(node);
|
|
13364
|
+
if (table) push(table, node.startPosition.row + 1);
|
|
13365
|
+
}
|
|
13366
|
+
});
|
|
13367
|
+
return out;
|
|
13368
|
+
}
|
|
13369
|
+
|
|
12680
13370
|
// src/extract/calls/index.ts
|
|
12681
13371
|
function edgeTypeFromEndpoint(ep) {
|
|
12682
13372
|
switch (ep.edgeType) {
|
|
@@ -12735,6 +13425,11 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
12735
13425
|
} catch (err) {
|
|
12736
13426
|
recordExtractionError("laravel eloquent extraction", file.path, err);
|
|
12737
13427
|
}
|
|
13428
|
+
try {
|
|
13429
|
+
endpoints.push(...efcoreEndpointsFromFile(file, service.dir));
|
|
13430
|
+
} catch (err) {
|
|
13431
|
+
recordExtractionError("efcore data-axis extraction", file.path, err);
|
|
13432
|
+
}
|
|
12738
13433
|
}
|
|
12739
13434
|
endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
|
|
12740
13435
|
endpoints.push(...pythonOrmCrossFileEndpoints(maskedFiles, service.dir));
|
|
@@ -12838,7 +13533,7 @@ import {
|
|
|
12838
13533
|
Provenance as Provenance16,
|
|
12839
13534
|
confidenceForExtracted as confidenceForExtracted13,
|
|
12840
13535
|
extractedEdgeId as extractedEdgeId10,
|
|
12841
|
-
infraId as
|
|
13536
|
+
infraId as infraId18
|
|
12842
13537
|
} from "@neat.is/types";
|
|
12843
13538
|
async function addTableEdges(graph, services) {
|
|
12844
13539
|
let nodesAdded = 0;
|
|
@@ -12867,8 +13562,8 @@ async function addTableEdges(graph, services) {
|
|
|
12867
13562
|
}
|
|
12868
13563
|
refs.push(...modelRefs);
|
|
12869
13564
|
for (const ref of refs) {
|
|
12870
|
-
const childId =
|
|
12871
|
-
const parentId =
|
|
13565
|
+
const childId = infraId18("sql-table", ref.childTable);
|
|
13566
|
+
const parentId = infraId18("sql-table", ref.parentTable);
|
|
12872
13567
|
if (childId === parentId) continue;
|
|
12873
13568
|
nodesAdded += ensureTableNode(graph, childId, ref.childTable);
|
|
12874
13569
|
nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
|
|
@@ -12903,14 +13598,14 @@ function ensureTableNode(graph, id, name) {
|
|
|
12903
13598
|
}
|
|
12904
13599
|
|
|
12905
13600
|
// src/extract/infra/docker-compose.ts
|
|
12906
|
-
import
|
|
13601
|
+
import path55 from "path";
|
|
12907
13602
|
import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
|
|
12908
13603
|
|
|
12909
13604
|
// src/extract/infra/shared.ts
|
|
12910
|
-
import { NodeType as NodeType26, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as
|
|
13605
|
+
import { NodeType as NodeType26, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId19 } from "@neat.is/types";
|
|
12911
13606
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
12912
13607
|
return {
|
|
12913
|
-
id:
|
|
13608
|
+
id: infraId19(kind, name),
|
|
12914
13609
|
type: NodeType26.InfraNode,
|
|
12915
13610
|
name,
|
|
12916
13611
|
provider,
|
|
@@ -12973,7 +13668,7 @@ function dependsOnList(value) {
|
|
|
12973
13668
|
}
|
|
12974
13669
|
function serviceNameToServiceNode(name, services) {
|
|
12975
13670
|
for (const s of services) {
|
|
12976
|
-
if (s.node.name === name ||
|
|
13671
|
+
if (s.node.name === name || path55.basename(s.dir) === name) return s.node.id;
|
|
12977
13672
|
}
|
|
12978
13673
|
return null;
|
|
12979
13674
|
}
|
|
@@ -12982,7 +13677,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
12982
13677
|
let edgesAdded = 0;
|
|
12983
13678
|
let composePath = null;
|
|
12984
13679
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
12985
|
-
const abs =
|
|
13680
|
+
const abs = path55.join(scanPath, name);
|
|
12986
13681
|
if (await exists(abs)) {
|
|
12987
13682
|
composePath = abs;
|
|
12988
13683
|
break;
|
|
@@ -12995,13 +13690,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
12995
13690
|
} catch (err) {
|
|
12996
13691
|
recordExtractionError(
|
|
12997
13692
|
"infra docker-compose",
|
|
12998
|
-
|
|
13693
|
+
path55.relative(scanPath, composePath),
|
|
12999
13694
|
err
|
|
13000
13695
|
);
|
|
13001
13696
|
return { nodesAdded, edgesAdded };
|
|
13002
13697
|
}
|
|
13003
13698
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
13004
|
-
const evidenceFile =
|
|
13699
|
+
const evidenceFile = path55.relative(scanPath, composePath).split(path55.sep).join("/");
|
|
13005
13700
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
13006
13701
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
13007
13702
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -13042,8 +13737,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
13042
13737
|
}
|
|
13043
13738
|
|
|
13044
13739
|
// src/extract/infra/dockerfile.ts
|
|
13045
|
-
import
|
|
13046
|
-
import { promises as
|
|
13740
|
+
import path56 from "path";
|
|
13741
|
+
import { promises as fs25 } from "fs";
|
|
13047
13742
|
import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
|
|
13048
13743
|
function readDockerfile(content) {
|
|
13049
13744
|
let image = null;
|
|
@@ -13073,15 +13768,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
13073
13768
|
let nodesAdded = 0;
|
|
13074
13769
|
let edgesAdded = 0;
|
|
13075
13770
|
for (const service of services) {
|
|
13076
|
-
const dockerfilePath =
|
|
13771
|
+
const dockerfilePath = path56.join(service.dir, "Dockerfile");
|
|
13077
13772
|
if (!await exists(dockerfilePath)) continue;
|
|
13078
13773
|
let content;
|
|
13079
13774
|
try {
|
|
13080
|
-
content = await
|
|
13775
|
+
content = await fs25.readFile(dockerfilePath, "utf8");
|
|
13081
13776
|
} catch (err) {
|
|
13082
13777
|
recordExtractionError(
|
|
13083
13778
|
"infra dockerfile",
|
|
13084
|
-
|
|
13779
|
+
path56.relative(scanPath, dockerfilePath),
|
|
13085
13780
|
err
|
|
13086
13781
|
);
|
|
13087
13782
|
continue;
|
|
@@ -13093,8 +13788,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
13093
13788
|
graph.addNode(node.id, node);
|
|
13094
13789
|
nodesAdded++;
|
|
13095
13790
|
}
|
|
13096
|
-
const relDockerfile = toPosix(
|
|
13097
|
-
const evidenceFile = toPosix(
|
|
13791
|
+
const relDockerfile = toPosix(path56.relative(service.dir, dockerfilePath));
|
|
13792
|
+
const evidenceFile = toPosix(path56.relative(scanPath, dockerfilePath));
|
|
13098
13793
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
13099
13794
|
graph,
|
|
13100
13795
|
service.pkg.name,
|
|
@@ -13145,23 +13840,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
13145
13840
|
}
|
|
13146
13841
|
|
|
13147
13842
|
// src/extract/infra/terraform.ts
|
|
13148
|
-
import { promises as
|
|
13149
|
-
import
|
|
13843
|
+
import { promises as fs26 } from "fs";
|
|
13844
|
+
import path57 from "path";
|
|
13150
13845
|
import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
|
|
13151
13846
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
13152
13847
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
13153
13848
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
13154
13849
|
if (depth > max) return [];
|
|
13155
13850
|
const out = [];
|
|
13156
|
-
const entries = await
|
|
13851
|
+
const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
13157
13852
|
for (const entry of entries) {
|
|
13158
13853
|
if (entry.isDirectory()) {
|
|
13159
13854
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
13160
|
-
const child =
|
|
13855
|
+
const child = path57.join(start, entry.name);
|
|
13161
13856
|
if (await isPythonVenvDir(child)) continue;
|
|
13162
13857
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
13163
13858
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
13164
|
-
out.push(
|
|
13859
|
+
out.push(path57.join(start, entry.name));
|
|
13165
13860
|
}
|
|
13166
13861
|
}
|
|
13167
13862
|
return out;
|
|
@@ -13192,8 +13887,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
13192
13887
|
let edgesAdded = 0;
|
|
13193
13888
|
const files = await walkTfFiles(scanPath);
|
|
13194
13889
|
for (const file of files) {
|
|
13195
|
-
const content = await
|
|
13196
|
-
const evidenceFile = toPosix(
|
|
13890
|
+
const content = await fs26.readFile(file, "utf8");
|
|
13891
|
+
const evidenceFile = toPosix(path57.relative(scanPath, file));
|
|
13197
13892
|
const resources = [];
|
|
13198
13893
|
const byKey = /* @__PURE__ */ new Map();
|
|
13199
13894
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -13249,8 +13944,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
13249
13944
|
}
|
|
13250
13945
|
|
|
13251
13946
|
// src/extract/infra/k8s.ts
|
|
13252
|
-
import { promises as
|
|
13253
|
-
import
|
|
13947
|
+
import { promises as fs27 } from "fs";
|
|
13948
|
+
import path58 from "path";
|
|
13254
13949
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
13255
13950
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
13256
13951
|
Service: "k8s-service",
|
|
@@ -13264,15 +13959,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
13264
13959
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
13265
13960
|
if (depth > max) return [];
|
|
13266
13961
|
const out = [];
|
|
13267
|
-
const entries = await
|
|
13962
|
+
const entries = await fs27.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
13268
13963
|
for (const entry of entries) {
|
|
13269
13964
|
if (entry.isDirectory()) {
|
|
13270
13965
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
13271
|
-
const child =
|
|
13966
|
+
const child = path58.join(start, entry.name);
|
|
13272
13967
|
if (await isPythonVenvDir(child)) continue;
|
|
13273
13968
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
13274
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
13275
|
-
out.push(
|
|
13969
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path58.extname(entry.name))) {
|
|
13970
|
+
out.push(path58.join(start, entry.name));
|
|
13276
13971
|
}
|
|
13277
13972
|
}
|
|
13278
13973
|
return out;
|
|
@@ -13281,7 +13976,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
13281
13976
|
let nodesAdded = 0;
|
|
13282
13977
|
const files = await walkYamlFiles2(scanPath);
|
|
13283
13978
|
for (const file of files) {
|
|
13284
|
-
const content = await
|
|
13979
|
+
const content = await fs27.readFile(file, "utf8");
|
|
13285
13980
|
let docs;
|
|
13286
13981
|
try {
|
|
13287
13982
|
docs = parseAllDocuments2(content).map((d) => d.toJSON());
|
|
@@ -13304,16 +13999,16 @@ async function addK8sResources(graph, scanPath) {
|
|
|
13304
13999
|
}
|
|
13305
14000
|
|
|
13306
14001
|
// src/extract/infra/cloudflare.ts
|
|
13307
|
-
import { promises as
|
|
13308
|
-
import
|
|
14002
|
+
import { promises as fs28 } from "fs";
|
|
14003
|
+
import path59 from "path";
|
|
13309
14004
|
import { parse as parseToml3 } from "smol-toml";
|
|
13310
14005
|
import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
|
|
13311
14006
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
13312
14007
|
async function readWranglerConfig(dir) {
|
|
13313
14008
|
for (const filename of WRANGLER_FILENAMES) {
|
|
13314
|
-
const abs =
|
|
14009
|
+
const abs = path59.join(dir, filename);
|
|
13315
14010
|
if (!await exists(abs)) continue;
|
|
13316
|
-
const raw = await
|
|
14011
|
+
const raw = await fs28.readFile(abs, "utf8");
|
|
13317
14012
|
const config = filename === "wrangler.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
13318
14013
|
return { config, relFile: filename, raw };
|
|
13319
14014
|
}
|
|
@@ -13374,11 +14069,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
13374
14069
|
try {
|
|
13375
14070
|
read = await readWranglerConfig(service.dir);
|
|
13376
14071
|
} catch (err) {
|
|
13377
|
-
recordExtractionError("infra cloudflare",
|
|
14072
|
+
recordExtractionError("infra cloudflare", path59.relative(scanPath, service.dir), err);
|
|
13378
14073
|
continue;
|
|
13379
14074
|
}
|
|
13380
14075
|
if (!read || !read.config.name) continue;
|
|
13381
|
-
const evidenceFile = toPosix(
|
|
14076
|
+
const evidenceFile = toPosix(path59.relative(scanPath, path59.join(service.dir, read.relFile)));
|
|
13382
14077
|
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
13383
14078
|
}
|
|
13384
14079
|
for (const worker of discovered) {
|
|
@@ -13390,7 +14085,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
13390
14085
|
}
|
|
13391
14086
|
let anchorId = service.node.id;
|
|
13392
14087
|
if (config.main) {
|
|
13393
|
-
const entryRelPath = toPosix(
|
|
14088
|
+
const entryRelPath = toPosix(path59.normalize(config.main));
|
|
13394
14089
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
13395
14090
|
graph,
|
|
13396
14091
|
service.pkg.name,
|
|
@@ -13536,24 +14231,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
13536
14231
|
}
|
|
13537
14232
|
|
|
13538
14233
|
// src/extract/infra/vercel.ts
|
|
13539
|
-
import { promises as
|
|
13540
|
-
import
|
|
14234
|
+
import { promises as fs29 } from "fs";
|
|
14235
|
+
import path60 from "path";
|
|
13541
14236
|
import { EdgeType as EdgeType21 } from "@neat.is/types";
|
|
13542
14237
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
13543
14238
|
async function readVercelConfig(dir) {
|
|
13544
14239
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
13545
|
-
const abs =
|
|
14240
|
+
const abs = path60.join(dir, filename);
|
|
13546
14241
|
if (!await exists(abs)) continue;
|
|
13547
|
-
const raw = await
|
|
14242
|
+
const raw = await fs29.readFile(abs, "utf8");
|
|
13548
14243
|
const config = JSON.parse(maskCommentsInSource(raw));
|
|
13549
14244
|
return { config, relFile: filename, raw };
|
|
13550
14245
|
}
|
|
13551
14246
|
return null;
|
|
13552
14247
|
}
|
|
13553
14248
|
async function readLinkedProjectName(dir) {
|
|
13554
|
-
const abs =
|
|
14249
|
+
const abs = path60.join(dir, ".vercel", "project.json");
|
|
13555
14250
|
if (!await exists(abs)) return void 0;
|
|
13556
|
-
const parsed = JSON.parse(await
|
|
14251
|
+
const parsed = JSON.parse(await fs29.readFile(abs, "utf8"));
|
|
13557
14252
|
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
13558
14253
|
}
|
|
13559
14254
|
function routeSource(route) {
|
|
@@ -13569,7 +14264,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
13569
14264
|
read = await readVercelConfig(service.dir);
|
|
13570
14265
|
projectName = await readLinkedProjectName(service.dir);
|
|
13571
14266
|
} catch (err) {
|
|
13572
|
-
recordExtractionError("infra vercel",
|
|
14267
|
+
recordExtractionError("infra vercel", path60.relative(scanPath, service.dir), err);
|
|
13573
14268
|
continue;
|
|
13574
14269
|
}
|
|
13575
14270
|
if (!read && !projectName) continue;
|
|
@@ -13585,7 +14280,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
13585
14280
|
const anchorId = service.node.id;
|
|
13586
14281
|
if (!read) continue;
|
|
13587
14282
|
const { config, relFile, raw } = read;
|
|
13588
|
-
const evidenceFile = toPosix(
|
|
14283
|
+
const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
|
|
13589
14284
|
const add = (edgeType, kind, name) => {
|
|
13590
14285
|
if (!name) return;
|
|
13591
14286
|
const result = emitPlatformResourceEdge(
|
|
@@ -13613,16 +14308,16 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
13613
14308
|
}
|
|
13614
14309
|
|
|
13615
14310
|
// src/extract/infra/railway.ts
|
|
13616
|
-
import { promises as
|
|
13617
|
-
import
|
|
14311
|
+
import { promises as fs30 } from "fs";
|
|
14312
|
+
import path61 from "path";
|
|
13618
14313
|
import { parse as parseToml4 } from "smol-toml";
|
|
13619
14314
|
import { EdgeType as EdgeType22 } from "@neat.is/types";
|
|
13620
14315
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
13621
14316
|
async function readRailwayConfig(dir) {
|
|
13622
14317
|
for (const filename of RAILWAY_FILENAMES) {
|
|
13623
|
-
const abs =
|
|
14318
|
+
const abs = path61.join(dir, filename);
|
|
13624
14319
|
if (!await exists(abs)) continue;
|
|
13625
|
-
const raw = await
|
|
14320
|
+
const raw = await fs30.readFile(abs, "utf8");
|
|
13626
14321
|
const config = filename === "railway.toml" ? parseToml4(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
13627
14322
|
return { config, relFile: filename, raw };
|
|
13628
14323
|
}
|
|
@@ -13636,7 +14331,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
13636
14331
|
try {
|
|
13637
14332
|
read = await readRailwayConfig(service.dir);
|
|
13638
14333
|
} catch (err) {
|
|
13639
|
-
recordExtractionError("infra railway",
|
|
14334
|
+
recordExtractionError("infra railway", path61.relative(scanPath, service.dir), err);
|
|
13640
14335
|
continue;
|
|
13641
14336
|
}
|
|
13642
14337
|
if (!read) continue;
|
|
@@ -13646,7 +14341,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
13646
14341
|
}
|
|
13647
14342
|
const anchorId = service.node.id;
|
|
13648
14343
|
const { config, relFile, raw } = read;
|
|
13649
|
-
const evidenceFile = toPosix(
|
|
14344
|
+
const evidenceFile = toPosix(path61.relative(scanPath, path61.join(service.dir, relFile)));
|
|
13650
14345
|
const add = (edgeType, kind, name) => {
|
|
13651
14346
|
if (!name) return;
|
|
13652
14347
|
const result = emitPlatformResourceEdge(
|
|
@@ -13670,15 +14365,15 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
13670
14365
|
}
|
|
13671
14366
|
|
|
13672
14367
|
// src/extract/infra/supabase.ts
|
|
13673
|
-
import { promises as
|
|
13674
|
-
import
|
|
14368
|
+
import { promises as fs31 } from "fs";
|
|
14369
|
+
import path62 from "path";
|
|
13675
14370
|
import { parse as parseToml5 } from "smol-toml";
|
|
13676
14371
|
import { EdgeType as EdgeType23 } from "@neat.is/types";
|
|
13677
14372
|
async function readSupabaseConfig(dir) {
|
|
13678
|
-
const relFile =
|
|
13679
|
-
const abs =
|
|
14373
|
+
const relFile = path62.join("supabase", "config.toml");
|
|
14374
|
+
const abs = path62.join(dir, relFile);
|
|
13680
14375
|
if (!await exists(abs)) return null;
|
|
13681
|
-
const raw = await
|
|
14376
|
+
const raw = await fs31.readFile(abs, "utf8");
|
|
13682
14377
|
const config = parseToml5(raw);
|
|
13683
14378
|
return { config, relFile, raw };
|
|
13684
14379
|
}
|
|
@@ -13690,7 +14385,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
13690
14385
|
try {
|
|
13691
14386
|
read = await readSupabaseConfig(service.dir);
|
|
13692
14387
|
} catch (err) {
|
|
13693
|
-
recordExtractionError("infra supabase",
|
|
14388
|
+
recordExtractionError("infra supabase", path62.relative(scanPath, service.dir), err);
|
|
13694
14389
|
continue;
|
|
13695
14390
|
}
|
|
13696
14391
|
if (!read) continue;
|
|
@@ -13705,7 +14400,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
13705
14400
|
});
|
|
13706
14401
|
}
|
|
13707
14402
|
const anchorId = service.node.id;
|
|
13708
|
-
const evidenceFile = toPosix(
|
|
14403
|
+
const evidenceFile = toPosix(path62.relative(scanPath, path62.join(service.dir, relFile)));
|
|
13709
14404
|
const add = (edgeType, kind, name) => {
|
|
13710
14405
|
if (!name) return;
|
|
13711
14406
|
const result = emitPlatformResourceEdge(
|
|
@@ -13746,8 +14441,8 @@ async function addInfra(graph, scanPath, services) {
|
|
|
13746
14441
|
}
|
|
13747
14442
|
|
|
13748
14443
|
// src/extract/zod-shapes.ts
|
|
13749
|
-
import
|
|
13750
|
-
import
|
|
14444
|
+
import path63 from "path";
|
|
14445
|
+
import Parser17 from "tree-sitter";
|
|
13751
14446
|
import JavaScript8 from "tree-sitter-javascript";
|
|
13752
14447
|
import {
|
|
13753
14448
|
EdgeType as EdgeType24,
|
|
@@ -13755,12 +14450,12 @@ import {
|
|
|
13755
14450
|
Provenance as Provenance22,
|
|
13756
14451
|
confidenceForExtracted as confidenceForExtracted19,
|
|
13757
14452
|
extractedEdgeId as extractedEdgeId11,
|
|
13758
|
-
infraId as
|
|
14453
|
+
infraId as infraId20
|
|
13759
14454
|
} from "@neat.is/types";
|
|
13760
14455
|
var ZOD_IMPORT_RE = /\bzod\b/;
|
|
13761
14456
|
var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
|
|
13762
14457
|
function parserForExt4(ext) {
|
|
13763
|
-
const p = new
|
|
14458
|
+
const p = new Parser17();
|
|
13764
14459
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript8);
|
|
13765
14460
|
return p;
|
|
13766
14461
|
}
|
|
@@ -13848,7 +14543,7 @@ function topLevelSchemas(root) {
|
|
|
13848
14543
|
}
|
|
13849
14544
|
function zodShapesFromFile(file, serviceDir) {
|
|
13850
14545
|
if (!ZOD_IMPORT_RE.test(file.content)) return [];
|
|
13851
|
-
const tree = parseSource3(parserForExt4(
|
|
14546
|
+
const tree = parseSource3(parserForExt4(path63.extname(file.path)), file.content);
|
|
13852
14547
|
const out = [];
|
|
13853
14548
|
const seen = /* @__PURE__ */ new Set();
|
|
13854
14549
|
for (const { name, call } of topLevelSchemas(tree.rootNode)) {
|
|
@@ -13862,11 +14557,11 @@ function zodShapesFromFile(file, serviceDir) {
|
|
|
13862
14557
|
seen.add(name);
|
|
13863
14558
|
const line = call.startPosition.row + 1;
|
|
13864
14559
|
out.push({
|
|
13865
|
-
infraId:
|
|
14560
|
+
infraId: infraId20("zod-schema", name),
|
|
13866
14561
|
name,
|
|
13867
14562
|
fields,
|
|
13868
14563
|
evidence: {
|
|
13869
|
-
file:
|
|
14564
|
+
file: path63.relative(serviceDir, file.path),
|
|
13870
14565
|
line,
|
|
13871
14566
|
snippet: snippet(file.content, line)
|
|
13872
14567
|
}
|
|
@@ -14102,11 +14797,11 @@ async function addFirestoreRules(graph, services) {
|
|
|
14102
14797
|
}
|
|
14103
14798
|
|
|
14104
14799
|
// src/extract/index.ts
|
|
14105
|
-
import
|
|
14800
|
+
import path65 from "path";
|
|
14106
14801
|
|
|
14107
14802
|
// src/extract/retire.ts
|
|
14108
14803
|
import { existsSync as existsSync2 } from "fs";
|
|
14109
|
-
import
|
|
14804
|
+
import path64 from "path";
|
|
14110
14805
|
import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
|
|
14111
14806
|
function dropOrphanedFileNodes(graph) {
|
|
14112
14807
|
const orphans = [];
|
|
@@ -14140,11 +14835,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
14140
14835
|
if (edge.provenance !== Provenance23.EXTRACTED) return;
|
|
14141
14836
|
const evidenceFile = edge.evidence?.file;
|
|
14142
14837
|
if (!evidenceFile) return;
|
|
14143
|
-
if (
|
|
14838
|
+
if (path64.isAbsolute(evidenceFile)) {
|
|
14144
14839
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
14145
14840
|
return;
|
|
14146
14841
|
}
|
|
14147
|
-
const found = bases.some((base) => existsSync2(
|
|
14842
|
+
const found = bases.some((base) => existsSync2(path64.join(base, evidenceFile)));
|
|
14148
14843
|
if (!found) toDrop.push(id);
|
|
14149
14844
|
});
|
|
14150
14845
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -14201,7 +14896,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
14201
14896
|
}
|
|
14202
14897
|
const droppedEntries = drainDroppedExtracted();
|
|
14203
14898
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
14204
|
-
const rejectedPath =
|
|
14899
|
+
const rejectedPath = path65.join(path65.dirname(opts.errorsPath), "rejected.ndjson");
|
|
14205
14900
|
try {
|
|
14206
14901
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
14207
14902
|
} catch (err) {
|
|
@@ -14575,8 +15270,8 @@ function computeDivergences(graph, opts = {}) {
|
|
|
14575
15270
|
}
|
|
14576
15271
|
|
|
14577
15272
|
// src/persist.ts
|
|
14578
|
-
import { promises as
|
|
14579
|
-
import
|
|
15273
|
+
import { promises as fs32 } from "fs";
|
|
15274
|
+
import path66 from "path";
|
|
14580
15275
|
import { NodeType as NodeType31, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
14581
15276
|
var SCHEMA_VERSION = 7;
|
|
14582
15277
|
function migrateV1ToV2(payload) {
|
|
@@ -14631,7 +15326,7 @@ function migrateV2ToV3(payload) {
|
|
|
14631
15326
|
return { ...payload, schemaVersion: 3 };
|
|
14632
15327
|
}
|
|
14633
15328
|
async function ensureDir(filePath) {
|
|
14634
|
-
await
|
|
15329
|
+
await fs32.mkdir(path66.dirname(filePath), { recursive: true });
|
|
14635
15330
|
}
|
|
14636
15331
|
async function saveGraphToDisk(graph, outPath) {
|
|
14637
15332
|
await ensureDir(outPath);
|
|
@@ -14641,13 +15336,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
14641
15336
|
graph: graph.export()
|
|
14642
15337
|
};
|
|
14643
15338
|
const tmp = `${outPath}.tmp`;
|
|
14644
|
-
await
|
|
14645
|
-
await
|
|
15339
|
+
await fs32.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
15340
|
+
await fs32.rename(tmp, outPath);
|
|
14646
15341
|
}
|
|
14647
15342
|
async function loadGraphFromDisk(graph, outPath) {
|
|
14648
15343
|
let raw;
|
|
14649
15344
|
try {
|
|
14650
|
-
raw = await
|
|
15345
|
+
raw = await fs32.readFile(outPath, "utf8");
|
|
14651
15346
|
} catch (err) {
|
|
14652
15347
|
if (err.code === "ENOENT") return;
|
|
14653
15348
|
throw err;
|
|
@@ -14720,7 +15415,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
14720
15415
|
}
|
|
14721
15416
|
|
|
14722
15417
|
// src/diff.ts
|
|
14723
|
-
import { promises as
|
|
15418
|
+
import { promises as fs33 } from "fs";
|
|
14724
15419
|
async function loadSnapshotForDiff(target) {
|
|
14725
15420
|
if (/^https?:\/\//i.test(target)) {
|
|
14726
15421
|
const res = await fetch(target);
|
|
@@ -14729,7 +15424,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
14729
15424
|
}
|
|
14730
15425
|
return await res.json();
|
|
14731
15426
|
}
|
|
14732
|
-
const raw = await
|
|
15427
|
+
const raw = await fs33.readFile(target, "utf8");
|
|
14733
15428
|
return JSON.parse(raw);
|
|
14734
15429
|
}
|
|
14735
15430
|
function indexEntries(entries) {
|
|
@@ -14796,23 +15491,23 @@ function canonicalJson(value) {
|
|
|
14796
15491
|
}
|
|
14797
15492
|
|
|
14798
15493
|
// src/projects.ts
|
|
14799
|
-
import
|
|
15494
|
+
import path67 from "path";
|
|
14800
15495
|
function pathsForProject(project, baseDir) {
|
|
14801
15496
|
if (project === DEFAULT_PROJECT) {
|
|
14802
15497
|
return {
|
|
14803
|
-
snapshotPath:
|
|
14804
|
-
errorsPath:
|
|
14805
|
-
staleEventsPath:
|
|
14806
|
-
embeddingsCachePath:
|
|
14807
|
-
policyViolationsPath:
|
|
15498
|
+
snapshotPath: path67.join(baseDir, "graph.json"),
|
|
15499
|
+
errorsPath: path67.join(baseDir, "errors.ndjson"),
|
|
15500
|
+
staleEventsPath: path67.join(baseDir, "stale-events.ndjson"),
|
|
15501
|
+
embeddingsCachePath: path67.join(baseDir, "embeddings.json"),
|
|
15502
|
+
policyViolationsPath: path67.join(baseDir, "policy-violations.ndjson")
|
|
14808
15503
|
};
|
|
14809
15504
|
}
|
|
14810
15505
|
return {
|
|
14811
|
-
snapshotPath:
|
|
14812
|
-
errorsPath:
|
|
14813
|
-
staleEventsPath:
|
|
14814
|
-
embeddingsCachePath:
|
|
14815
|
-
policyViolationsPath:
|
|
15506
|
+
snapshotPath: path67.join(baseDir, `${project}.json`),
|
|
15507
|
+
errorsPath: path67.join(baseDir, `errors.${project}.ndjson`),
|
|
15508
|
+
staleEventsPath: path67.join(baseDir, `stale-events.${project}.ndjson`),
|
|
15509
|
+
embeddingsCachePath: path67.join(baseDir, `embeddings.${project}.json`),
|
|
15510
|
+
policyViolationsPath: path67.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
14816
15511
|
};
|
|
14817
15512
|
}
|
|
14818
15513
|
var Projects = class {
|
|
@@ -14851,9 +15546,9 @@ function parseExtraProjects(raw) {
|
|
|
14851
15546
|
}
|
|
14852
15547
|
|
|
14853
15548
|
// src/registry.ts
|
|
14854
|
-
import { promises as
|
|
15549
|
+
import { promises as fs34 } from "fs";
|
|
14855
15550
|
import os2 from "os";
|
|
14856
|
-
import
|
|
15551
|
+
import path68 from "path";
|
|
14857
15552
|
import {
|
|
14858
15553
|
RegistryFileSchema
|
|
14859
15554
|
} from "@neat.is/types";
|
|
@@ -14861,20 +15556,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
14861
15556
|
var LOCK_RETRY_MS = 50;
|
|
14862
15557
|
function neatHome() {
|
|
14863
15558
|
const override = process.env.NEAT_HOME;
|
|
14864
|
-
if (override && override.length > 0) return
|
|
14865
|
-
return
|
|
15559
|
+
if (override && override.length > 0) return path68.resolve(override);
|
|
15560
|
+
return path68.join(os2.homedir(), ".neat");
|
|
14866
15561
|
}
|
|
14867
15562
|
function registryPath() {
|
|
14868
|
-
return
|
|
15563
|
+
return path68.join(neatHome(), "projects.json");
|
|
14869
15564
|
}
|
|
14870
15565
|
function registryLockPath() {
|
|
14871
|
-
return
|
|
15566
|
+
return path68.join(neatHome(), "projects.json.lock");
|
|
14872
15567
|
}
|
|
14873
15568
|
function daemonPidPath() {
|
|
14874
|
-
return
|
|
15569
|
+
return path68.join(neatHome(), "neatd.pid");
|
|
14875
15570
|
}
|
|
14876
15571
|
function daemonsDir() {
|
|
14877
|
-
return
|
|
15572
|
+
return path68.join(neatHome(), "daemons");
|
|
14878
15573
|
}
|
|
14879
15574
|
function isFiniteInt(v) {
|
|
14880
15575
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -14907,7 +15602,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
14907
15602
|
const dir = daemonsDir();
|
|
14908
15603
|
let names;
|
|
14909
15604
|
try {
|
|
14910
|
-
names = await
|
|
15605
|
+
names = await fs34.readdir(dir);
|
|
14911
15606
|
} catch (err) {
|
|
14912
15607
|
if (err.code === "ENOENT") return [];
|
|
14913
15608
|
throw err;
|
|
@@ -14915,10 +15610,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
14915
15610
|
const out = [];
|
|
14916
15611
|
for (const name of names) {
|
|
14917
15612
|
if (!name.endsWith(".json")) continue;
|
|
14918
|
-
const file =
|
|
15613
|
+
const file = path68.join(dir, name);
|
|
14919
15614
|
let raw;
|
|
14920
15615
|
try {
|
|
14921
|
-
raw = await
|
|
15616
|
+
raw = await fs34.readFile(file, "utf8");
|
|
14922
15617
|
} catch {
|
|
14923
15618
|
continue;
|
|
14924
15619
|
}
|
|
@@ -14931,7 +15626,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
14931
15626
|
return out;
|
|
14932
15627
|
}
|
|
14933
15628
|
async function removeDaemonRecord(source) {
|
|
14934
|
-
await
|
|
15629
|
+
await fs34.unlink(source).catch(() => {
|
|
14935
15630
|
});
|
|
14936
15631
|
}
|
|
14937
15632
|
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
@@ -14988,7 +15683,7 @@ function isPidAliveDefault(pid) {
|
|
|
14988
15683
|
}
|
|
14989
15684
|
async function readPidFile(file) {
|
|
14990
15685
|
try {
|
|
14991
|
-
const raw = await
|
|
15686
|
+
const raw = await fs34.readFile(file, "utf8");
|
|
14992
15687
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
14993
15688
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
14994
15689
|
} catch {
|
|
@@ -15036,32 +15731,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
15036
15731
|
}
|
|
15037
15732
|
}
|
|
15038
15733
|
async function normalizeProjectPath(input) {
|
|
15039
|
-
const resolved =
|
|
15734
|
+
const resolved = path68.resolve(input);
|
|
15040
15735
|
try {
|
|
15041
|
-
return await
|
|
15736
|
+
return await fs34.realpath(resolved);
|
|
15042
15737
|
} catch {
|
|
15043
15738
|
return resolved;
|
|
15044
15739
|
}
|
|
15045
15740
|
}
|
|
15046
15741
|
async function writeAtomically(target, contents) {
|
|
15047
|
-
await
|
|
15742
|
+
await fs34.mkdir(path68.dirname(target), { recursive: true });
|
|
15048
15743
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
15049
|
-
const fd = await
|
|
15744
|
+
const fd = await fs34.open(tmp, "w");
|
|
15050
15745
|
try {
|
|
15051
15746
|
await fd.writeFile(contents, "utf8");
|
|
15052
15747
|
await fd.sync();
|
|
15053
15748
|
} finally {
|
|
15054
15749
|
await fd.close();
|
|
15055
15750
|
}
|
|
15056
|
-
await
|
|
15751
|
+
await fs34.rename(tmp, target);
|
|
15057
15752
|
}
|
|
15058
15753
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
15059
15754
|
const deadline = Date.now() + timeoutMs;
|
|
15060
|
-
await
|
|
15755
|
+
await fs34.mkdir(path68.dirname(lockPath), { recursive: true });
|
|
15061
15756
|
let probedHolder = false;
|
|
15062
15757
|
while (true) {
|
|
15063
15758
|
try {
|
|
15064
|
-
const fd = await
|
|
15759
|
+
const fd = await fs34.open(lockPath, "wx");
|
|
15065
15760
|
try {
|
|
15066
15761
|
await fd.writeFile(`${process.pid}
|
|
15067
15762
|
`, "utf8");
|
|
@@ -15086,7 +15781,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
15086
15781
|
}
|
|
15087
15782
|
}
|
|
15088
15783
|
async function releaseLock(lockPath) {
|
|
15089
|
-
await
|
|
15784
|
+
await fs34.unlink(lockPath).catch(() => {
|
|
15090
15785
|
});
|
|
15091
15786
|
}
|
|
15092
15787
|
async function withLock(fn) {
|
|
@@ -15102,7 +15797,7 @@ async function readRegistry() {
|
|
|
15102
15797
|
const file = registryPath();
|
|
15103
15798
|
let raw;
|
|
15104
15799
|
try {
|
|
15105
|
-
raw = await
|
|
15800
|
+
raw = await fs34.readFile(file, "utf8");
|
|
15106
15801
|
} catch (err) {
|
|
15107
15802
|
if (err.code === "ENOENT") {
|
|
15108
15803
|
return { version: 1, projects: [] };
|
|
@@ -15207,7 +15902,7 @@ function pruneTtlMs() {
|
|
|
15207
15902
|
}
|
|
15208
15903
|
async function statPathStatus(p) {
|
|
15209
15904
|
try {
|
|
15210
|
-
const stat = await
|
|
15905
|
+
const stat = await fs34.stat(p);
|
|
15211
15906
|
return stat.isDirectory() ? "present" : "unknown";
|
|
15212
15907
|
} catch (err) {
|
|
15213
15908
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -15252,14 +15947,14 @@ import cors from "@fastify/cors";
|
|
|
15252
15947
|
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
15253
15948
|
|
|
15254
15949
|
// src/extend/index.ts
|
|
15255
|
-
import { promises as
|
|
15256
|
-
import
|
|
15950
|
+
import { promises as fs36 } from "fs";
|
|
15951
|
+
import path70 from "path";
|
|
15257
15952
|
import os3 from "os";
|
|
15258
15953
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
15259
15954
|
|
|
15260
15955
|
// src/installers/package-manager.ts
|
|
15261
|
-
import { promises as
|
|
15262
|
-
import
|
|
15956
|
+
import { promises as fs35 } from "fs";
|
|
15957
|
+
import path69 from "path";
|
|
15263
15958
|
import { spawn } from "child_process";
|
|
15264
15959
|
var LOCKFILE_PRIORITY = [
|
|
15265
15960
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -15274,29 +15969,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
15274
15969
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
15275
15970
|
async function exists2(p) {
|
|
15276
15971
|
try {
|
|
15277
|
-
await
|
|
15972
|
+
await fs35.access(p);
|
|
15278
15973
|
return true;
|
|
15279
15974
|
} catch {
|
|
15280
15975
|
return false;
|
|
15281
15976
|
}
|
|
15282
15977
|
}
|
|
15283
15978
|
async function detectPackageManager(serviceDir) {
|
|
15284
|
-
let dir =
|
|
15979
|
+
let dir = path69.resolve(serviceDir);
|
|
15285
15980
|
const stops = /* @__PURE__ */ new Set();
|
|
15286
15981
|
for (let i = 0; i < 64; i++) {
|
|
15287
15982
|
if (stops.has(dir)) break;
|
|
15288
15983
|
stops.add(dir);
|
|
15289
15984
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
15290
|
-
const lockPath =
|
|
15985
|
+
const lockPath = path69.join(dir, candidate.lockfile);
|
|
15291
15986
|
if (await exists2(lockPath)) {
|
|
15292
15987
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
15293
15988
|
}
|
|
15294
15989
|
}
|
|
15295
|
-
const parent =
|
|
15990
|
+
const parent = path69.dirname(dir);
|
|
15296
15991
|
if (parent === dir) break;
|
|
15297
15992
|
dir = parent;
|
|
15298
15993
|
}
|
|
15299
|
-
return { pm: "npm", cwd:
|
|
15994
|
+
return { pm: "npm", cwd: path69.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
15300
15995
|
}
|
|
15301
15996
|
async function runPackageManagerInstall(cmd) {
|
|
15302
15997
|
return new Promise((resolve) => {
|
|
@@ -15338,15 +16033,15 @@ ${err.message}`
|
|
|
15338
16033
|
// src/extend/index.ts
|
|
15339
16034
|
async function fileExists2(p) {
|
|
15340
16035
|
try {
|
|
15341
|
-
await
|
|
16036
|
+
await fs36.access(p);
|
|
15342
16037
|
return true;
|
|
15343
16038
|
} catch {
|
|
15344
16039
|
return false;
|
|
15345
16040
|
}
|
|
15346
16041
|
}
|
|
15347
16042
|
async function readPackageJson(scanPath) {
|
|
15348
|
-
const pkgPath =
|
|
15349
|
-
const raw = await
|
|
16043
|
+
const pkgPath = path70.join(scanPath, "package.json");
|
|
16044
|
+
const raw = await fs36.readFile(pkgPath, "utf8");
|
|
15350
16045
|
return JSON.parse(raw);
|
|
15351
16046
|
}
|
|
15352
16047
|
var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -15359,27 +16054,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
15359
16054
|
]);
|
|
15360
16055
|
async function findHookFiles(scanPath) {
|
|
15361
16056
|
const found = [];
|
|
15362
|
-
const
|
|
15363
|
-
const entries = await
|
|
16057
|
+
const walk10 = async (dir) => {
|
|
16058
|
+
const entries = await fs36.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
15364
16059
|
for (const entry of entries) {
|
|
15365
16060
|
if (entry.isDirectory()) {
|
|
15366
16061
|
if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
|
|
15367
|
-
await
|
|
16062
|
+
await walk10(path70.join(dir, entry.name));
|
|
15368
16063
|
} else if (entry.isFile()) {
|
|
15369
16064
|
if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
|
|
15370
|
-
const rel =
|
|
15371
|
-
found.push(rel.split(
|
|
16065
|
+
const rel = path70.relative(scanPath, path70.join(dir, entry.name));
|
|
16066
|
+
found.push(rel.split(path70.sep).join("/"));
|
|
15372
16067
|
}
|
|
15373
16068
|
}
|
|
15374
16069
|
}
|
|
15375
16070
|
};
|
|
15376
|
-
await
|
|
16071
|
+
await walk10(scanPath);
|
|
15377
16072
|
return found.sort();
|
|
15378
16073
|
}
|
|
15379
16074
|
async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
15380
16075
|
let fallback = null;
|
|
15381
16076
|
for (const file of hookFiles) {
|
|
15382
|
-
const content = await
|
|
16077
|
+
const content = await fs36.readFile(path70.join(scanPath, file), "utf8");
|
|
15383
16078
|
const patched = splicedContent(content, snippet2);
|
|
15384
16079
|
if (patched !== null) return { file, content, patched };
|
|
15385
16080
|
if (fallback === null) fallback = { file, content };
|
|
@@ -15387,12 +16082,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
|
15387
16082
|
return { file: fallback.file, content: fallback.content, patched: null };
|
|
15388
16083
|
}
|
|
15389
16084
|
function extendLogPath() {
|
|
15390
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
16085
|
+
return process.env.NEAT_EXTEND_LOG ?? path70.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
15391
16086
|
}
|
|
15392
16087
|
async function appendExtendLog(entry) {
|
|
15393
16088
|
const logPath = extendLogPath();
|
|
15394
|
-
await
|
|
15395
|
-
await
|
|
16089
|
+
await fs36.mkdir(path70.dirname(logPath), { recursive: true });
|
|
16090
|
+
await fs36.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
15396
16091
|
}
|
|
15397
16092
|
function splicedContent(fileContent, snippet2) {
|
|
15398
16093
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -15450,7 +16145,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
15450
16145
|
}
|
|
15451
16146
|
async function describeProjectInstrumentation(ctx) {
|
|
15452
16147
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
15453
|
-
const envNeat = await fileExists2(
|
|
16148
|
+
const envNeat = await fileExists2(path70.join(ctx.scanPath, ".env.neat"));
|
|
15454
16149
|
const registryInstrPackages = new Set(
|
|
15455
16150
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
15456
16151
|
);
|
|
@@ -15472,7 +16167,7 @@ async function applyExtension(ctx, args, options) {
|
|
|
15472
16167
|
);
|
|
15473
16168
|
}
|
|
15474
16169
|
for (const file of hookFiles) {
|
|
15475
|
-
const content = await
|
|
16170
|
+
const content = await fs36.readFile(path70.join(ctx.scanPath, file), "utf8");
|
|
15476
16171
|
if (content.includes(args.registration_snippet)) {
|
|
15477
16172
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
15478
16173
|
}
|
|
@@ -15484,18 +16179,18 @@ async function applyExtension(ctx, args, options) {
|
|
|
15484
16179
|
);
|
|
15485
16180
|
}
|
|
15486
16181
|
const primaryFile = primary.file;
|
|
15487
|
-
const primaryPath =
|
|
16182
|
+
const primaryPath = path70.join(ctx.scanPath, primaryFile);
|
|
15488
16183
|
const filesTouched = [];
|
|
15489
16184
|
const depsAdded = [];
|
|
15490
|
-
const pkgPath =
|
|
16185
|
+
const pkgPath = path70.join(ctx.scanPath, "package.json");
|
|
15491
16186
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
15492
16187
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
15493
16188
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
15494
|
-
await
|
|
16189
|
+
await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
15495
16190
|
filesTouched.push("package.json");
|
|
15496
16191
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
15497
16192
|
}
|
|
15498
|
-
await
|
|
16193
|
+
await fs36.writeFile(primaryPath, primary.patched, "utf8");
|
|
15499
16194
|
filesTouched.push(primaryFile);
|
|
15500
16195
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
15501
16196
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -15526,7 +16221,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
15526
16221
|
};
|
|
15527
16222
|
}
|
|
15528
16223
|
for (const file of hookFiles) {
|
|
15529
|
-
const content = await
|
|
16224
|
+
const content = await fs36.readFile(path70.join(ctx.scanPath, file), "utf8");
|
|
15530
16225
|
if (content.includes(args.registration_snippet)) {
|
|
15531
16226
|
return {
|
|
15532
16227
|
library: args.library,
|
|
@@ -15561,28 +16256,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
15561
16256
|
if (!await fileExists2(logPath)) {
|
|
15562
16257
|
return { undone: false, message: "no apply found for library" };
|
|
15563
16258
|
}
|
|
15564
|
-
const raw = await
|
|
16259
|
+
const raw = await fs36.readFile(logPath, "utf8");
|
|
15565
16260
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
15566
16261
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
15567
16262
|
if (!match) {
|
|
15568
16263
|
return { undone: false, message: "no apply found for library" };
|
|
15569
16264
|
}
|
|
15570
|
-
const pkgPath =
|
|
16265
|
+
const pkgPath = path70.join(ctx.scanPath, "package.json");
|
|
15571
16266
|
if (await fileExists2(pkgPath)) {
|
|
15572
16267
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
15573
16268
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
15574
16269
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
15575
16270
|
pkg.dependencies = rest;
|
|
15576
|
-
await
|
|
16271
|
+
await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
15577
16272
|
}
|
|
15578
16273
|
}
|
|
15579
16274
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
15580
16275
|
for (const file of hookFiles) {
|
|
15581
|
-
const filePath =
|
|
15582
|
-
const content = await
|
|
16276
|
+
const filePath = path70.join(ctx.scanPath, file);
|
|
16277
|
+
const content = await fs36.readFile(filePath, "utf8");
|
|
15583
16278
|
if (content.includes(match.registration_snippet)) {
|
|
15584
16279
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
15585
|
-
await
|
|
16280
|
+
await fs36.writeFile(filePath, filtered, "utf8");
|
|
15586
16281
|
break;
|
|
15587
16282
|
}
|
|
15588
16283
|
}
|
|
@@ -15693,8 +16388,8 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
15693
16388
|
|
|
15694
16389
|
// src/connectors-config.ts
|
|
15695
16390
|
import os4 from "os";
|
|
15696
|
-
import
|
|
15697
|
-
import { promises as
|
|
16391
|
+
import path71 from "path";
|
|
16392
|
+
import { promises as fs37 } from "fs";
|
|
15698
16393
|
var CONNECTORS_CONFIG_VERSION = 1;
|
|
15699
16394
|
var EnvRefUnsetError = class extends Error {
|
|
15700
16395
|
ref;
|
|
@@ -15708,17 +16403,17 @@ var EnvRefUnsetError = class extends Error {
|
|
|
15708
16403
|
};
|
|
15709
16404
|
function neatHome2() {
|
|
15710
16405
|
const override = process.env.NEAT_HOME;
|
|
15711
|
-
if (override && override.length > 0) return
|
|
15712
|
-
return
|
|
16406
|
+
if (override && override.length > 0) return path71.resolve(override);
|
|
16407
|
+
return path71.join(os4.homedir(), ".neat");
|
|
15713
16408
|
}
|
|
15714
16409
|
function connectorsConfigPath(home = neatHome2()) {
|
|
15715
|
-
return
|
|
16410
|
+
return path71.join(home, "connectors.json");
|
|
15716
16411
|
}
|
|
15717
16412
|
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
15718
16413
|
async function warnIfModeLooserThan0600(file) {
|
|
15719
16414
|
if (process.platform === "win32") return;
|
|
15720
16415
|
try {
|
|
15721
|
-
const stat = await
|
|
16416
|
+
const stat = await fs37.stat(file);
|
|
15722
16417
|
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
15723
16418
|
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
15724
16419
|
console.warn(
|
|
@@ -15732,7 +16427,7 @@ async function readConnectorsConfig(home = neatHome2()) {
|
|
|
15732
16427
|
const file = connectorsConfigPath(home);
|
|
15733
16428
|
let raw;
|
|
15734
16429
|
try {
|
|
15735
|
-
raw = await
|
|
16430
|
+
raw = await fs37.readFile(file, "utf8");
|
|
15736
16431
|
} catch (err) {
|
|
15737
16432
|
if (err.code === "ENOENT") {
|
|
15738
16433
|
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
@@ -15843,7 +16538,7 @@ function connectorMatchesProject(entry, project) {
|
|
|
15843
16538
|
var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
|
|
15844
16539
|
var CONNECTORS_LOCK_RETRY_MS = 50;
|
|
15845
16540
|
function connectorsConfigLockPath(home = neatHome2()) {
|
|
15846
|
-
return
|
|
16541
|
+
return path71.join(home, "connectors.json.lock");
|
|
15847
16542
|
}
|
|
15848
16543
|
function isEnvRef(value) {
|
|
15849
16544
|
return value.length > 1 && value.startsWith("$");
|
|
@@ -15856,9 +16551,9 @@ function redactCredentialRef(ref) {
|
|
|
15856
16551
|
return out;
|
|
15857
16552
|
}
|
|
15858
16553
|
async function writeConfigAtomically0600(file, contents) {
|
|
15859
|
-
await
|
|
16554
|
+
await fs37.mkdir(path71.dirname(file), { recursive: true });
|
|
15860
16555
|
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
15861
|
-
const fd = await
|
|
16556
|
+
const fd = await fs37.open(tmp, "w", 384);
|
|
15862
16557
|
try {
|
|
15863
16558
|
await fd.writeFile(contents, "utf8");
|
|
15864
16559
|
await fd.chmod(384);
|
|
@@ -15866,14 +16561,14 @@ async function writeConfigAtomically0600(file, contents) {
|
|
|
15866
16561
|
} finally {
|
|
15867
16562
|
await fd.close();
|
|
15868
16563
|
}
|
|
15869
|
-
await
|
|
16564
|
+
await fs37.rename(tmp, file);
|
|
15870
16565
|
}
|
|
15871
16566
|
async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
|
|
15872
16567
|
const deadline = Date.now() + timeoutMs;
|
|
15873
|
-
await
|
|
16568
|
+
await fs37.mkdir(path71.dirname(lockPath), { recursive: true });
|
|
15874
16569
|
for (; ; ) {
|
|
15875
16570
|
try {
|
|
15876
|
-
const fd = await
|
|
16571
|
+
const fd = await fs37.open(lockPath, "wx");
|
|
15877
16572
|
try {
|
|
15878
16573
|
await fd.writeFile(`${process.pid}
|
|
15879
16574
|
`, "utf8");
|
|
@@ -15893,7 +16588,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
|
|
|
15893
16588
|
}
|
|
15894
16589
|
}
|
|
15895
16590
|
async function releaseConnectorsLock(lockPath) {
|
|
15896
|
-
await
|
|
16591
|
+
await fs37.unlink(lockPath).catch(() => {
|
|
15897
16592
|
});
|
|
15898
16593
|
}
|
|
15899
16594
|
async function withConnectorsLock(home, fn) {
|
|
@@ -16522,10 +17217,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
16522
17217
|
// src/connectors/supabase/map.ts
|
|
16523
17218
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
16524
17219
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
16525
|
-
function targetFromRestPath(
|
|
16526
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
17220
|
+
function targetFromRestPath(path72) {
|
|
17221
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path72);
|
|
16527
17222
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
16528
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
17223
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path72);
|
|
16529
17224
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
16530
17225
|
return null;
|
|
16531
17226
|
}
|
|
@@ -16634,21 +17329,21 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
16634
17329
|
}
|
|
16635
17330
|
|
|
16636
17331
|
// src/connectors/supabase/resolve.ts
|
|
16637
|
-
import { EdgeType as EdgeType26, infraId as
|
|
17332
|
+
import { EdgeType as EdgeType26, infraId as infraId21 } from "@neat.is/types";
|
|
16638
17333
|
function createSupabaseResolveTarget(graph, config) {
|
|
16639
17334
|
return (signal, _ctx) => {
|
|
16640
17335
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
16641
17336
|
return null;
|
|
16642
17337
|
}
|
|
16643
|
-
const subResourceId =
|
|
17338
|
+
const subResourceId = infraId21(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
16644
17339
|
if (graph.hasNode(subResourceId)) {
|
|
16645
17340
|
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
|
|
16646
17341
|
}
|
|
16647
|
-
const bareResourceId =
|
|
17342
|
+
const bareResourceId = infraId21(signal.targetKind, signal.targetName);
|
|
16648
17343
|
if (graph.hasNode(bareResourceId)) {
|
|
16649
17344
|
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
|
|
16650
17345
|
}
|
|
16651
|
-
const projectLevelId =
|
|
17346
|
+
const projectLevelId = infraId21("supabase", config.nodeRef);
|
|
16652
17347
|
if (graph.hasNode(projectLevelId)) {
|
|
16653
17348
|
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
|
|
16654
17349
|
}
|
|
@@ -17120,9 +17815,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
17120
17815
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
17121
17816
|
if (secondSep === -1) return null;
|
|
17122
17817
|
const method = rest.slice(0, secondSep);
|
|
17123
|
-
const
|
|
17124
|
-
if (!resourceName || !method || !
|
|
17125
|
-
return { resourceName, method, path:
|
|
17818
|
+
const path72 = rest.slice(secondSep + 1);
|
|
17819
|
+
if (!resourceName || !method || !path72) return null;
|
|
17820
|
+
return { resourceName, method, path: path72 };
|
|
17126
17821
|
}
|
|
17127
17822
|
function resourceNameFor(type, labels) {
|
|
17128
17823
|
if (!labels) return null;
|
|
@@ -17160,14 +17855,14 @@ function mapLogEntryToSignal(entry) {
|
|
|
17160
17855
|
if (!req) return null;
|
|
17161
17856
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
17162
17857
|
const method = req.requestMethod.toUpperCase();
|
|
17163
|
-
const
|
|
17164
|
-
if (
|
|
17858
|
+
const path72 = pathFromRequestUrl(req.requestUrl);
|
|
17859
|
+
if (path72 === null) return null;
|
|
17165
17860
|
const timestamp = entry.timestamp;
|
|
17166
17861
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
17167
17862
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
17168
17863
|
return {
|
|
17169
17864
|
targetKind: resourceType,
|
|
17170
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
17865
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path72 }),
|
|
17171
17866
|
callCount: 1,
|
|
17172
17867
|
errorCount: isError ? 1 : 0,
|
|
17173
17868
|
lastObservedIso: timestamp
|
|
@@ -17253,7 +17948,7 @@ function createFirebaseConnector(graph, serviceMap) {
|
|
|
17253
17948
|
}
|
|
17254
17949
|
|
|
17255
17950
|
// src/connectors/cloudflare/connector.ts
|
|
17256
|
-
import { EdgeType as EdgeType29, NodeType as NodeType35, fileId as fileId5, infraId as
|
|
17951
|
+
import { EdgeType as EdgeType29, NodeType as NodeType35, fileId as fileId5, infraId as infraId22 } from "@neat.is/types";
|
|
17257
17952
|
|
|
17258
17953
|
// src/connectors/cloudflare/client.ts
|
|
17259
17954
|
import { randomUUID } from "crypto";
|
|
@@ -17364,7 +18059,7 @@ function mapEventToSignal(event) {
|
|
|
17364
18059
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
17365
18060
|
const statusCode = metadata?.statusCode;
|
|
17366
18061
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
17367
|
-
const
|
|
18062
|
+
const path72 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
17368
18063
|
return {
|
|
17369
18064
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
17370
18065
|
targetName: scriptName,
|
|
@@ -17372,7 +18067,7 @@ function mapEventToSignal(event) {
|
|
|
17372
18067
|
errorCount: isError ? 1 : 0,
|
|
17373
18068
|
lastObservedIso: observedAt.toISOString(),
|
|
17374
18069
|
method,
|
|
17375
|
-
...
|
|
18070
|
+
...path72 ? { path: path72 } : {},
|
|
17376
18071
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
17377
18072
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
17378
18073
|
};
|
|
@@ -17418,8 +18113,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
17418
18113
|
});
|
|
17419
18114
|
return found;
|
|
17420
18115
|
}
|
|
17421
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
17422
|
-
const normalizedPath = normalizePathTemplate(
|
|
18116
|
+
function findMatchingRouteNode(graph, serviceName, method, path72) {
|
|
18117
|
+
const normalizedPath = normalizePathTemplate(path72);
|
|
17423
18118
|
let found = null;
|
|
17424
18119
|
graph.forEachNode((id, attrs) => {
|
|
17425
18120
|
if (found) return;
|
|
@@ -17436,10 +18131,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
17436
18131
|
return (signal) => {
|
|
17437
18132
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
17438
18133
|
const scriptName = signal.targetName;
|
|
17439
|
-
const { method, path:
|
|
18134
|
+
const { method, path: path72 } = signal;
|
|
17440
18135
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
17441
|
-
if (!method || !
|
|
17442
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
18136
|
+
if (!method || !path72) return wholeFileId;
|
|
18137
|
+
return findMatchingRouteNode(graph, serviceName, method, path72) ?? wholeFileId;
|
|
17443
18138
|
};
|
|
17444
18139
|
const mapping = config.workers?.[scriptName];
|
|
17445
18140
|
if (mapping) {
|
|
@@ -17460,7 +18155,7 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
17460
18155
|
};
|
|
17461
18156
|
}
|
|
17462
18157
|
return {
|
|
17463
|
-
targetNodeId:
|
|
18158
|
+
targetNodeId: infraId22("cloudflare-worker", scriptName),
|
|
17464
18159
|
serviceName: scriptName,
|
|
17465
18160
|
edgeType: EdgeType29.CALLS,
|
|
17466
18161
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
@@ -17644,12 +18339,12 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
17644
18339
|
}
|
|
17645
18340
|
|
|
17646
18341
|
// src/connectors/neon/resolve.ts
|
|
17647
|
-
import { EdgeType as EdgeType30, infraId as
|
|
18342
|
+
import { EdgeType as EdgeType30, infraId as infraId23 } from "@neat.is/types";
|
|
17648
18343
|
function createNeonResolveTarget(config) {
|
|
17649
18344
|
return (signal) => {
|
|
17650
18345
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
17651
18346
|
return {
|
|
17652
|
-
targetNodeId:
|
|
18347
|
+
targetNodeId: infraId23("sql-table", signal.targetName),
|
|
17653
18348
|
serviceName: config.serviceName,
|
|
17654
18349
|
edgeType: EdgeType30.CALLS,
|
|
17655
18350
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
@@ -17769,9 +18464,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
17769
18464
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
17770
18465
|
if (secondSep === -1) return null;
|
|
17771
18466
|
const method = rest.slice(0, secondSep);
|
|
17772
|
-
const
|
|
17773
|
-
if (!serviceName || !method || !
|
|
17774
|
-
return { serviceName, method, path:
|
|
18467
|
+
const path72 = rest.slice(secondSep + 1);
|
|
18468
|
+
if (!serviceName || !method || !path72) return null;
|
|
18469
|
+
return { serviceName, method, path: path72 };
|
|
17775
18470
|
}
|
|
17776
18471
|
|
|
17777
18472
|
// src/connectors/cloud-run/map.ts
|
|
@@ -17800,14 +18495,14 @@ function mapLogEntryToSignal2(entry) {
|
|
|
17800
18495
|
if (!req) return null;
|
|
17801
18496
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
17802
18497
|
const method = req.requestMethod.toUpperCase();
|
|
17803
|
-
const
|
|
17804
|
-
if (
|
|
18498
|
+
const path72 = pathFromRequestUrl2(req.requestUrl);
|
|
18499
|
+
if (path72 === null) return null;
|
|
17805
18500
|
const timestamp = entry.timestamp;
|
|
17806
18501
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
17807
18502
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
17808
18503
|
return {
|
|
17809
18504
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
17810
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
18505
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path72 }),
|
|
17811
18506
|
callCount: 1,
|
|
17812
18507
|
errorCount: isError ? 1 : 0,
|
|
17813
18508
|
lastObservedIso: timestamp
|
|
@@ -17823,7 +18518,7 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
17823
18518
|
}
|
|
17824
18519
|
|
|
17825
18520
|
// src/connectors/cloud-run/resolve.ts
|
|
17826
|
-
import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as
|
|
18521
|
+
import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as infraId24 } from "@neat.is/types";
|
|
17827
18522
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
17828
18523
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
17829
18524
|
let found = null;
|
|
@@ -17845,21 +18540,21 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
17845
18540
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
17846
18541
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
17847
18542
|
if (!identity) return null;
|
|
17848
|
-
const { serviceName: gcpServiceName, method, path:
|
|
18543
|
+
const { serviceName: gcpServiceName, method, path: path72 } = identity;
|
|
17849
18544
|
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
17850
18545
|
if (mappedService) {
|
|
17851
18546
|
const routeNodeId = findMatchingRouteNode2(
|
|
17852
18547
|
graph,
|
|
17853
18548
|
mappedService,
|
|
17854
18549
|
method,
|
|
17855
|
-
normalizePathTemplate(
|
|
18550
|
+
normalizePathTemplate(path72)
|
|
17856
18551
|
);
|
|
17857
18552
|
if (routeNodeId) {
|
|
17858
18553
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
|
|
17859
18554
|
}
|
|
17860
18555
|
}
|
|
17861
18556
|
return {
|
|
17862
|
-
targetNodeId:
|
|
18557
|
+
targetNodeId: infraId24(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
17863
18558
|
serviceName: mappedService ?? gcpServiceName,
|
|
17864
18559
|
edgeType: EdgeType31.CALLS,
|
|
17865
18560
|
ensureInfraNode: {
|
|
@@ -18191,17 +18886,17 @@ function mapInsightsToSignals(rows, observedAtIso) {
|
|
|
18191
18886
|
}
|
|
18192
18887
|
|
|
18193
18888
|
// src/connectors/planetscale/resolve.ts
|
|
18194
|
-
import { EdgeType as EdgeType33, infraId as
|
|
18889
|
+
import { EdgeType as EdgeType33, infraId as infraId25 } from "@neat.is/types";
|
|
18195
18890
|
var PLANETSCALE_DATABASE_KIND = "planetscale-database";
|
|
18196
18891
|
function createPlanetscaleResolveTarget(graph, config) {
|
|
18197
18892
|
const databaseName = `${config.organization}/${config.database}`;
|
|
18198
18893
|
return (signal, _ctx) => {
|
|
18199
18894
|
if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
18200
|
-
const tableId =
|
|
18895
|
+
const tableId = infraId25("sql-table", signal.targetName);
|
|
18201
18896
|
if (graph.hasNode(tableId)) {
|
|
18202
18897
|
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: EdgeType33.CALLS };
|
|
18203
18898
|
}
|
|
18204
|
-
const providerId =
|
|
18899
|
+
const providerId = infraId25(PLANETSCALE_DATABASE_KIND, databaseName);
|
|
18205
18900
|
return {
|
|
18206
18901
|
targetNodeId: providerId,
|
|
18207
18902
|
serviceName: config.serviceName,
|
|
@@ -20101,4 +20796,4 @@ export {
|
|
|
20101
20796
|
deprovisionConnector,
|
|
20102
20797
|
buildApi
|
|
20103
20798
|
};
|
|
20104
|
-
//# sourceMappingURL=chunk-
|
|
20799
|
+
//# sourceMappingURL=chunk-RQQUI3NQ.js.map
|