@neat.is/core 0.9.1-dev.20260819 → 0.9.1
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-JUVJBH23.js → chunk-UN7VFA4H.js} +1045 -376
- package/dist/chunk-UN7VFA4H.js.map +1 -0
- package/dist/{chunk-ICO37D3H.js → chunk-YKQB622D.js} +2 -2
- package/dist/cli.cjs +1581 -910
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +1244 -573
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +1260 -589
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +1147 -476
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-JUVJBH23.js.map +0 -1
- /package/dist/{chunk-ICO37D3H.js.map → chunk-YKQB622D.js.map} +0 -0
|
@@ -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];
|
|
@@ -4367,6 +4672,12 @@ async function advance4xxBurst(ctx, span, affectedNode, ts, nowMs, status) {
|
|
|
4367
4672
|
);
|
|
4368
4673
|
ctx.burstState.delete(key);
|
|
4369
4674
|
}
|
|
4675
|
+
var NEXT_API_ROUTE_SPAN_NAME = /^executing api route \((?:pages|app)\) (\/\S*)$/;
|
|
4676
|
+
function nextApiRouteTemplate(span) {
|
|
4677
|
+
const raw = pickAttr(span, "next.span_name") ?? span.name;
|
|
4678
|
+
const match = raw ? NEXT_API_ROUTE_SPAN_NAME.exec(raw) : null;
|
|
4679
|
+
return match ? match[1] : void 0;
|
|
4680
|
+
}
|
|
4370
4681
|
function findRouteNodeByHttpRoute(graph, serviceName, method, httpRoute) {
|
|
4371
4682
|
const target = normalizePathTemplate(httpRoute);
|
|
4372
4683
|
const m = method?.toUpperCase();
|
|
@@ -4389,7 +4700,7 @@ async function handleSpan(ctx, span) {
|
|
|
4389
4700
|
}
|
|
4390
4701
|
const sourceId = ensureServiceNode(ctx.graph, span.service, env);
|
|
4391
4702
|
const isError = span.statusCode === 2;
|
|
4392
|
-
const durationMs = span.durationNanos > 0n ? Number(span.durationNanos) / 1e6 : void 0;
|
|
4703
|
+
const durationMs = span.durationNanos > 0n && !spanIsStreaming(span) ? Number(span.durationNanos) / 1e6 : void 0;
|
|
4393
4704
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
4394
4705
|
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
4395
4706
|
cacheSpanService(span, nowMs, callSite);
|
|
@@ -4586,12 +4897,13 @@ async function handleSpan(ctx, span) {
|
|
|
4586
4897
|
}
|
|
4587
4898
|
}
|
|
4588
4899
|
}
|
|
4589
|
-
|
|
4900
|
+
const fusionRoute = nextApiRouteTemplate(span) ?? span.httpRoute;
|
|
4901
|
+
if (fusionRoute && (span.kind === 2 || span.kind === 1 || span.kind === 0 || span.kind === void 0)) {
|
|
4590
4902
|
const routeNodeId = findRouteNodeByHttpRoute(
|
|
4591
4903
|
ctx.graph,
|
|
4592
4904
|
span.service,
|
|
4593
4905
|
span.httpMethod,
|
|
4594
|
-
|
|
4906
|
+
fusionRoute
|
|
4595
4907
|
);
|
|
4596
4908
|
if (routeNodeId) {
|
|
4597
4909
|
const routeSvc = ctx.graph.getNodeAttributes(routeNodeId).service;
|
|
@@ -5008,19 +5320,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
5008
5320
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
5009
5321
|
let best = { path: [start], edges: [] };
|
|
5010
5322
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
5011
|
-
function step(node,
|
|
5012
|
-
if (
|
|
5013
|
-
best = { path: [...
|
|
5323
|
+
function step(node, path72, edges) {
|
|
5324
|
+
if (path72.length > best.path.length) {
|
|
5325
|
+
best = { path: [...path72], edges: [...edges] };
|
|
5014
5326
|
}
|
|
5015
|
-
if (
|
|
5327
|
+
if (path72.length - 1 >= maxDepth) return;
|
|
5016
5328
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
5017
5329
|
for (const [srcId, edge] of incoming) {
|
|
5018
5330
|
if (visited.has(srcId)) continue;
|
|
5019
5331
|
visited.add(srcId);
|
|
5020
|
-
|
|
5332
|
+
path72.push(srcId);
|
|
5021
5333
|
edges.push(edge);
|
|
5022
|
-
step(srcId,
|
|
5023
|
-
|
|
5334
|
+
step(srcId, path72, edges);
|
|
5335
|
+
path72.pop();
|
|
5024
5336
|
edges.pop();
|
|
5025
5337
|
visited.delete(srcId);
|
|
5026
5338
|
}
|
|
@@ -5028,11 +5340,11 @@ function longestIncomingWalk(graph, start, maxDepth) {
|
|
|
5028
5340
|
step(start, [start], []);
|
|
5029
5341
|
return best;
|
|
5030
5342
|
}
|
|
5031
|
-
function databaseRootCauseShape(graph, origin,
|
|
5343
|
+
function databaseRootCauseShape(graph, origin, walk10) {
|
|
5032
5344
|
const targetDb = origin;
|
|
5033
5345
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
5034
5346
|
if (candidatePairs.length === 0) return null;
|
|
5035
|
-
for (const id of
|
|
5347
|
+
for (const id of walk10.path) {
|
|
5036
5348
|
const owner = resolveOwningService(graph, id);
|
|
5037
5349
|
if (!owner) continue;
|
|
5038
5350
|
const { id: serviceId15, svc } = owner;
|
|
@@ -5059,8 +5371,8 @@ function databaseRootCauseShape(graph, origin, walk9) {
|
|
|
5059
5371
|
}
|
|
5060
5372
|
return null;
|
|
5061
5373
|
}
|
|
5062
|
-
function serviceRootCauseShape(graph, _origin,
|
|
5063
|
-
for (const id of
|
|
5374
|
+
function serviceRootCauseShape(graph, _origin, walk10) {
|
|
5375
|
+
for (const id of walk10.path) {
|
|
5064
5376
|
const owner = resolveOwningService(graph, id);
|
|
5065
5377
|
if (!owner) continue;
|
|
5066
5378
|
const { id: serviceId15, svc } = owner;
|
|
@@ -5096,15 +5408,15 @@ function serviceRootCauseShape(graph, _origin, walk9) {
|
|
|
5096
5408
|
}
|
|
5097
5409
|
return null;
|
|
5098
5410
|
}
|
|
5099
|
-
function fileRootCauseShape(graph, origin,
|
|
5411
|
+
function fileRootCauseShape(graph, origin, walk10) {
|
|
5100
5412
|
const owner = resolveOwningService(graph, origin.id);
|
|
5101
5413
|
if (!owner) return null;
|
|
5102
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
5414
|
+
return serviceRootCauseShape(graph, owner.svc, walk10);
|
|
5103
5415
|
}
|
|
5104
|
-
function symbolRootCauseShape(graph, origin,
|
|
5416
|
+
function symbolRootCauseShape(graph, origin, walk10) {
|
|
5105
5417
|
const owner = resolveOwningService(graph, origin.id);
|
|
5106
5418
|
if (!owner) return null;
|
|
5107
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
5419
|
+
return serviceRootCauseShape(graph, owner.svc, walk10);
|
|
5108
5420
|
}
|
|
5109
5421
|
var rootCauseShapes = {
|
|
5110
5422
|
[NodeType5.DatabaseNode]: databaseRootCauseShape,
|
|
@@ -5117,25 +5429,29 @@ function legacyRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
5117
5429
|
const origin = graph.getNodeAttributes(errorNodeId);
|
|
5118
5430
|
const shape = rootCauseShapes[origin.type];
|
|
5119
5431
|
if (shape) {
|
|
5120
|
-
const
|
|
5121
|
-
const match = shape(graph, origin,
|
|
5432
|
+
const walk10 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
|
|
5433
|
+
const match = shape(graph, origin, walk10);
|
|
5122
5434
|
if (match) {
|
|
5123
5435
|
const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
|
|
5124
|
-
return
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5436
|
+
return {
|
|
5437
|
+
source: "compat",
|
|
5438
|
+
result: RootCauseResultSchema.parse({
|
|
5439
|
+
rootCauseNode: match.rootCauseNode,
|
|
5440
|
+
rootCauseReason: reason,
|
|
5441
|
+
traversalPath: walk10.path,
|
|
5442
|
+
edgeProvenances: walk10.edges.map((e) => e.provenance),
|
|
5443
|
+
confidence: confidenceFromMix(walk10.edges),
|
|
5444
|
+
fixRecommendation: match.fixRecommendation
|
|
5445
|
+
})
|
|
5446
|
+
};
|
|
5132
5447
|
}
|
|
5133
5448
|
}
|
|
5134
5449
|
if (origin.type === NodeType5.ServiceNode) {
|
|
5135
5450
|
const crossService = crossServiceRootCause(graph, errorNodeId, incidents, errorEvent);
|
|
5136
|
-
if (crossService) return crossService;
|
|
5451
|
+
if (crossService) return { result: crossService, source: "cross-service" };
|
|
5137
5452
|
}
|
|
5138
|
-
|
|
5453
|
+
const incident = rootCauseFromIncidents(errorNodeId, incidents, errorEvent);
|
|
5454
|
+
return incident ? { result: incident, source: "incident" } : null;
|
|
5139
5455
|
}
|
|
5140
5456
|
var INCIDENT_ROOT_CAUSE_CONFIDENCE = 0.6;
|
|
5141
5457
|
function incidentMatchesNode(ev, nodeId) {
|
|
@@ -5231,26 +5547,75 @@ function dominantFailingCall(graph, serviceId15, visited) {
|
|
|
5231
5547
|
return best;
|
|
5232
5548
|
}
|
|
5233
5549
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
5234
|
-
const
|
|
5550
|
+
const path72 = [originServiceId];
|
|
5235
5551
|
const edges = [];
|
|
5236
5552
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
5237
5553
|
let current = originServiceId;
|
|
5238
5554
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
5239
5555
|
const hop = dominantFailingCall(graph, current, visited);
|
|
5240
5556
|
if (!hop) break;
|
|
5241
|
-
|
|
5557
|
+
path72.push(hop.nextService);
|
|
5242
5558
|
edges.push(hop.edge);
|
|
5243
5559
|
visited.add(hop.nextService);
|
|
5244
5560
|
current = hop.nextService;
|
|
5245
5561
|
}
|
|
5246
5562
|
if (edges.length === 0) return null;
|
|
5247
|
-
return { path:
|
|
5563
|
+
return { path: path72, edges, culprit: current };
|
|
5564
|
+
}
|
|
5565
|
+
function isStaleCallEdge(e) {
|
|
5566
|
+
return e.type === EdgeType6.CALLS && e.provenance === Provenance6.STALE;
|
|
5567
|
+
}
|
|
5568
|
+
function staleCallDominates(e, id, curEdge, curId) {
|
|
5569
|
+
const ev = e.signal?.spanCount ?? e.callCount ?? 0;
|
|
5570
|
+
const cv = curEdge.signal?.spanCount ?? curEdge.callCount ?? 0;
|
|
5571
|
+
if (ev !== cv) return ev > cv;
|
|
5572
|
+
return id < curId;
|
|
5573
|
+
}
|
|
5574
|
+
function dominantStaleCall(graph, serviceId15, visited) {
|
|
5575
|
+
const bestByCallee = /* @__PURE__ */ new Map();
|
|
5576
|
+
for (const src of callSourcesForService(graph, serviceId15)) {
|
|
5577
|
+
for (const edgeId of graph.outboundEdges(src)) {
|
|
5578
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
5579
|
+
if (e.type !== EdgeType6.CALLS) continue;
|
|
5580
|
+
if (isFrontierNode(graph, e.target)) continue;
|
|
5581
|
+
const owner = resolveOwningService(graph, e.target);
|
|
5582
|
+
if (!owner || visited.has(owner.id)) continue;
|
|
5583
|
+
const cur = bestByCallee.get(owner.id);
|
|
5584
|
+
if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {
|
|
5585
|
+
bestByCallee.set(owner.id, e);
|
|
5586
|
+
}
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
let best = null;
|
|
5590
|
+
for (const [id, edge] of bestByCallee) {
|
|
5591
|
+
if (!isStaleCallEdge(edge)) continue;
|
|
5592
|
+
if (!best || staleCallDominates(edge, id, best.edge, best.nextService)) {
|
|
5593
|
+
best = { nextService: id, edge };
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5596
|
+
return best;
|
|
5597
|
+
}
|
|
5598
|
+
function followStaleCallChain(graph, originServiceId, maxDepth) {
|
|
5599
|
+
const path72 = [originServiceId];
|
|
5600
|
+
const edges = [];
|
|
5601
|
+
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
5602
|
+
let current = originServiceId;
|
|
5603
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
5604
|
+
const hop = dominantStaleCall(graph, current, visited);
|
|
5605
|
+
if (!hop) break;
|
|
5606
|
+
path72.push(hop.nextService);
|
|
5607
|
+
edges.push(hop.edge);
|
|
5608
|
+
visited.add(hop.nextService);
|
|
5609
|
+
current = hop.nextService;
|
|
5610
|
+
}
|
|
5611
|
+
if (edges.length === 0) return null;
|
|
5612
|
+
return { path: path72, edges, culprit: current };
|
|
5248
5613
|
}
|
|
5249
5614
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
5250
5615
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
5251
5616
|
if (!chain) return null;
|
|
5252
5617
|
const culprit = chain.culprit;
|
|
5253
|
-
const
|
|
5618
|
+
const path72 = [...chain.path];
|
|
5254
5619
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
5255
5620
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
5256
5621
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -5258,14 +5623,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5258
5623
|
if (loc) {
|
|
5259
5624
|
let rootCauseNode = culprit;
|
|
5260
5625
|
if (loc.fileNode) {
|
|
5261
|
-
|
|
5626
|
+
path72.push(loc.fileNode);
|
|
5262
5627
|
edgeProvenances.push(Provenance6.OBSERVED);
|
|
5263
5628
|
rootCauseNode = loc.fileNode;
|
|
5264
5629
|
}
|
|
5265
5630
|
return RootCauseResultSchema.parse({
|
|
5266
5631
|
rootCauseNode,
|
|
5267
5632
|
rootCauseReason: loc.rootCauseReason,
|
|
5268
|
-
traversalPath:
|
|
5633
|
+
traversalPath: path72,
|
|
5269
5634
|
edgeProvenances,
|
|
5270
5635
|
confidence,
|
|
5271
5636
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -5277,7 +5642,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5277
5642
|
return RootCauseResultSchema.parse({
|
|
5278
5643
|
rootCauseNode: culprit,
|
|
5279
5644
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
5280
|
-
traversalPath:
|
|
5645
|
+
traversalPath: path72,
|
|
5281
5646
|
edgeProvenances,
|
|
5282
5647
|
confidence,
|
|
5283
5648
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -5664,17 +6029,20 @@ function displayNameOf(nodeId) {
|
|
|
5664
6029
|
return nodeId.replace(/^[a-z]+:/, "");
|
|
5665
6030
|
}
|
|
5666
6031
|
function getRootCause(graph, errorNodeId, errorEvent, incidents, opts) {
|
|
5667
|
-
const
|
|
5668
|
-
if (!
|
|
6032
|
+
const tagged = legacyRootCause(graph, errorNodeId, errorEvent, incidents);
|
|
6033
|
+
if (!tagged) return null;
|
|
5669
6034
|
const navigation = opts?.navigation ?? process.env.NEAT_RCA_NAVIGATION !== "0";
|
|
5670
|
-
if (!navigation) return
|
|
5671
|
-
return enrichWithNavigation(graph, errorNodeId,
|
|
6035
|
+
if (!navigation) return tagged.result;
|
|
6036
|
+
return enrichWithNavigation(graph, errorNodeId, tagged, incidents, opts?.now ?? Date.now());
|
|
5672
6037
|
}
|
|
5673
|
-
function enrichWithNavigation(graph, errorNodeId,
|
|
6038
|
+
function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
6039
|
+
const legacy = tagged.result;
|
|
5674
6040
|
const seedNode = legacy.rootCauseNode;
|
|
5675
6041
|
const seedCtx = graph.hasNode(seedNode) ? nodeContext(graph, seedNode, incidents, now) : null;
|
|
5676
6042
|
const lastProv = legacy.edgeProvenances[legacy.edgeProvenances.length - 1];
|
|
5677
6043
|
const candidates = [];
|
|
6044
|
+
const deadEndOnSymptom = tagged.source === "incident" && seedNode === errorNodeId && legacy.traversalPath.length === 1;
|
|
6045
|
+
const staleChain = deadEndOnSymptom && !(seedCtx && isVictimSeed(seedCtx)) ? followStaleCallChain(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH) : null;
|
|
5678
6046
|
if (seedCtx && isVictimSeed(seedCtx)) {
|
|
5679
6047
|
const origin = findLoadOrigin(graph, errorNodeId, incidents, now);
|
|
5680
6048
|
const staleNote = seedCtx.stale ? "; it has gone STALE under load" : "";
|
|
@@ -5699,6 +6067,27 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
|
|
|
5699
6067
|
confidence: Math.min(legacy.confidence, 0.4),
|
|
5700
6068
|
...lastProv ? { provenance: lastProv } : {}
|
|
5701
6069
|
});
|
|
6070
|
+
} else if (staleChain) {
|
|
6071
|
+
const culprit = staleChain.culprit;
|
|
6072
|
+
const culpritName = displayNameOf(culprit);
|
|
6073
|
+
const seedName = displayNameOf(seedNode);
|
|
6074
|
+
const staleConfidence = confidenceFromMix(staleChain.edges, now);
|
|
6075
|
+
candidates.push({
|
|
6076
|
+
node: culprit,
|
|
6077
|
+
classification: "primary-failure",
|
|
6078
|
+
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.`,
|
|
6079
|
+
context: nodeContext(graph, culprit, incidents, now),
|
|
6080
|
+
confidence: staleConfidence,
|
|
6081
|
+
provenance: Provenance6.STALE
|
|
6082
|
+
});
|
|
6083
|
+
candidates.push({
|
|
6084
|
+
node: seedNode,
|
|
6085
|
+
classification: "symptom-only",
|
|
6086
|
+
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.`,
|
|
6087
|
+
context: seedCtx ?? EMPTY_CONTEXT,
|
|
6088
|
+
confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.STALE),
|
|
6089
|
+
...lastProv ? { provenance: lastProv } : {}
|
|
6090
|
+
});
|
|
5702
6091
|
} else {
|
|
5703
6092
|
candidates.push({
|
|
5704
6093
|
node: seedNode,
|
|
@@ -5712,11 +6101,14 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
|
|
|
5712
6101
|
const top = candidates[0];
|
|
5713
6102
|
let traversalPath = legacy.traversalPath;
|
|
5714
6103
|
let edgeProvenances = legacy.edgeProvenances;
|
|
5715
|
-
if (top.node
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
6104
|
+
if (staleChain && top.node === staleChain.culprit) {
|
|
6105
|
+
traversalPath = staleChain.path;
|
|
6106
|
+
edgeProvenances = staleChain.edges.map((e) => e.provenance);
|
|
6107
|
+
} else if (top.node !== seedNode) {
|
|
6108
|
+
const path72 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
|
|
6109
|
+
if (path72) {
|
|
6110
|
+
traversalPath = path72.nodes;
|
|
6111
|
+
edgeProvenances = path72.edges.map((e) => e.provenance);
|
|
5720
6112
|
} else {
|
|
5721
6113
|
traversalPath = [errorNodeId, top.node];
|
|
5722
6114
|
edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
|
|
@@ -5738,6 +6130,9 @@ function fixRecommendationForTop(top, seedNode, legacy) {
|
|
|
5738
6130
|
return legacy.fixRecommendation;
|
|
5739
6131
|
}
|
|
5740
6132
|
const name = top.node.replace(/^service:/, "");
|
|
6133
|
+
if (top.provenance === Provenance6.STALE) {
|
|
6134
|
+
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}.`;
|
|
6135
|
+
}
|
|
5741
6136
|
if (top.classification === "primary-failure") {
|
|
5742
6137
|
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
6138
|
}
|
|
@@ -6827,7 +7222,7 @@ import Php2 from "tree-sitter-php";
|
|
|
6827
7222
|
import CSharp from "tree-sitter-c-sharp";
|
|
6828
7223
|
import Java from "tree-sitter-java";
|
|
6829
7224
|
import Kotlin from "tree-sitter-kotlin";
|
|
6830
|
-
import
|
|
7225
|
+
import Rust2 from "tree-sitter-rust";
|
|
6831
7226
|
import Cpp from "tree-sitter-cpp";
|
|
6832
7227
|
import {
|
|
6833
7228
|
EdgeType as EdgeType7,
|
|
@@ -6855,7 +7250,7 @@ var SYMBOL_GRAMMAR_BY_EXT = {
|
|
|
6855
7250
|
".cs": CSharp,
|
|
6856
7251
|
".java": Java,
|
|
6857
7252
|
".kt": Kotlin,
|
|
6858
|
-
".rs":
|
|
7253
|
+
".rs": Rust2,
|
|
6859
7254
|
// C++ (ADR-202) — only the UNAMBIGUOUS extensions. `.cpp` / `.cc` / `.cxx` /
|
|
6860
7255
|
// `.c++` are implementation files; `.hpp` / `.hh` / `.hxx` / `.h++` are C++-only
|
|
6861
7256
|
// headers. `.h` and `.c` are deliberately absent: they are shared with C (a
|
|
@@ -7295,15 +7690,15 @@ function collectKotlinSymbolDefs(root) {
|
|
|
7295
7690
|
});
|
|
7296
7691
|
};
|
|
7297
7692
|
const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
|
|
7298
|
-
const
|
|
7693
|
+
const firstChildOfType2 = (node, types) => {
|
|
7299
7694
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
7300
7695
|
const child = node.namedChild(i);
|
|
7301
7696
|
if (child && types.includes(child.type)) return child;
|
|
7302
7697
|
}
|
|
7303
7698
|
return void 0;
|
|
7304
7699
|
};
|
|
7305
|
-
const nameOf = (node, ...types) =>
|
|
7306
|
-
const bodyOf = (node) =>
|
|
7700
|
+
const nameOf = (node, ...types) => firstChildOfType2(node, types)?.text;
|
|
7701
|
+
const bodyOf = (node) => firstChildOfType2(node, ["class_body", "enum_class_body"]);
|
|
7307
7702
|
let pkg;
|
|
7308
7703
|
for (let i = 0; i < root.namedChildCount; i++) {
|
|
7309
7704
|
const child = root.namedChild(i);
|
|
@@ -7810,7 +8205,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
7810
8205
|
return best;
|
|
7811
8206
|
};
|
|
7812
8207
|
const requests = [];
|
|
7813
|
-
const
|
|
8208
|
+
const walk10 = (node) => {
|
|
7814
8209
|
if (node.type === "class_declaration" || node.type === "abstract_class_declaration" || node.type === "class") {
|
|
7815
8210
|
const self = localBySpan.get(`${node.startPosition.row + 1}:${node.endPosition.row + 1}`);
|
|
7816
8211
|
if (self && self.kind === "class") {
|
|
@@ -7856,10 +8251,10 @@ async function addSymbolEdges(graph, services) {
|
|
|
7856
8251
|
}
|
|
7857
8252
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
7858
8253
|
const child = node.namedChild(i);
|
|
7859
|
-
if (child)
|
|
8254
|
+
if (child) walk10(child);
|
|
7860
8255
|
}
|
|
7861
8256
|
};
|
|
7862
|
-
|
|
8257
|
+
walk10(root);
|
|
7863
8258
|
for (const req of requests) {
|
|
7864
8259
|
const targetSid = resolveTarget(req.targetName, req.wantKind);
|
|
7865
8260
|
if (!targetSid) continue;
|
|
@@ -8160,7 +8555,7 @@ async function addServerActions(graph, services) {
|
|
|
8160
8555
|
}
|
|
8161
8556
|
|
|
8162
8557
|
// src/extract/databases/index.ts
|
|
8163
|
-
import
|
|
8558
|
+
import path34 from "path";
|
|
8164
8559
|
import {
|
|
8165
8560
|
EdgeType as EdgeType10,
|
|
8166
8561
|
NodeType as NodeType20,
|
|
@@ -8593,8 +8988,161 @@ async function parse8(serviceDir) {
|
|
|
8593
8988
|
}
|
|
8594
8989
|
var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
8595
8990
|
|
|
8596
|
-
// src/extract/databases/
|
|
8991
|
+
// src/extract/databases/csharp.ts
|
|
8992
|
+
import { promises as fs22 } from "fs";
|
|
8597
8993
|
import path32 from "path";
|
|
8994
|
+
var CS_EXT = ".cs";
|
|
8995
|
+
var NPGSQL_GATE = /\bUseNpgsql\b|\bNpgsql\b/;
|
|
8996
|
+
var REDIS_GATE = /\bConnectionMultiplexer\b|\bStackExchange\.Redis\b|\bConfigurationOptions\.Parse\b|\bAddStackExchangeRedisCache\b/;
|
|
8997
|
+
var ENV_READ_RE = /(?:GetEnvironmentVariable|GetConnectionString)\(\s*"([^"]+)"\s*\)|Configuration\s*\[\s*"([^"]+)"\s*\]/g;
|
|
8998
|
+
var STRING_LITERAL_RE = /@?"([^"\\]*(?:\\.[^"\\]*)*)"/g;
|
|
8999
|
+
function hostIsUnresolved(host) {
|
|
9000
|
+
return host === "" || /[${}]/.test(host);
|
|
9001
|
+
}
|
|
9002
|
+
function looksLikePostgres(s) {
|
|
9003
|
+
return /(?:^|;)\s*(?:host|server|data\s*source)\s*=/i.test(s) || /^postgres(?:ql)?:\/\//i.test(s);
|
|
9004
|
+
}
|
|
9005
|
+
function looksLikeRedis(s) {
|
|
9006
|
+
return /^rediss?:\/\//i.test(s) || /^[A-Za-z0-9_.-]+:\d+(?:$|,)/.test(s) || /,\s*(?:ssl|abortconnect|allowadmin|connecttimeout|password|user)\s*=/i.test(s);
|
|
9007
|
+
}
|
|
9008
|
+
function parsePostgresConnection(raw) {
|
|
9009
|
+
const s = raw.trim();
|
|
9010
|
+
if (/^postgres(?:ql)?:\/\//i.test(s)) return parseConnectionString(s);
|
|
9011
|
+
const fields = /* @__PURE__ */ new Map();
|
|
9012
|
+
for (const part of s.split(";")) {
|
|
9013
|
+
const eq = part.indexOf("=");
|
|
9014
|
+
if (eq < 0) continue;
|
|
9015
|
+
const key = part.slice(0, eq).trim().toLowerCase().replace(/\s+/g, " ");
|
|
9016
|
+
const value = part.slice(eq + 1).trim();
|
|
9017
|
+
if (value && !fields.has(key)) fields.set(key, value);
|
|
9018
|
+
}
|
|
9019
|
+
const hostRaw = fields.get("host") ?? fields.get("server") ?? fields.get("data source");
|
|
9020
|
+
if (!hostRaw) return null;
|
|
9021
|
+
const host = hostRaw.split(",")[0].trim();
|
|
9022
|
+
if (hostIsUnresolved(host)) return null;
|
|
9023
|
+
const portRaw = fields.get("port");
|
|
9024
|
+
const port = portRaw && /^\d+$/.test(portRaw) ? Number(portRaw) : void 0;
|
|
9025
|
+
const database = fields.get("database") ?? fields.get("db") ?? "";
|
|
9026
|
+
return { host, port, database, engine: "postgresql", engineVersion: "unknown" };
|
|
9027
|
+
}
|
|
9028
|
+
function parseRedisEndpoint(raw) {
|
|
9029
|
+
const s = raw.trim();
|
|
9030
|
+
if (/^rediss?:\/\//i.test(s)) return parseConnectionString(s);
|
|
9031
|
+
const first = s.split(",")[0].trim();
|
|
9032
|
+
const m = first.match(/^([A-Za-z0-9_.-]+)(?::(\d+))?$/);
|
|
9033
|
+
if (!m) return null;
|
|
9034
|
+
const host = m[1];
|
|
9035
|
+
if (hostIsUnresolved(host) || host.includes("=")) return null;
|
|
9036
|
+
const port = m[2] ? Number(m[2]) : void 0;
|
|
9037
|
+
return { host, port, database: "", engine: "redis", engineVersion: "unknown" };
|
|
9038
|
+
}
|
|
9039
|
+
async function resolveEnvUpTree(startDir, name) {
|
|
9040
|
+
let dir = path32.resolve(startDir);
|
|
9041
|
+
for (let depth = 0; depth < 12; depth++) {
|
|
9042
|
+
const value = await resolveEnvVar(dir, name);
|
|
9043
|
+
if (value !== null) return value;
|
|
9044
|
+
const atRepoRoot = await fs22.access(path32.join(dir, ".git")).then(() => true).catch(() => false);
|
|
9045
|
+
const parent = path32.dirname(dir);
|
|
9046
|
+
if (atRepoRoot || parent === dir) break;
|
|
9047
|
+
dir = parent;
|
|
9048
|
+
}
|
|
9049
|
+
return null;
|
|
9050
|
+
}
|
|
9051
|
+
async function interpolateEnvRefs(value, dir) {
|
|
9052
|
+
const refs = /* @__PURE__ */ new Set();
|
|
9053
|
+
for (const m of value.matchAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g)) {
|
|
9054
|
+
refs.add(m[1] ?? m[2]);
|
|
9055
|
+
}
|
|
9056
|
+
let out = value;
|
|
9057
|
+
for (const name of refs) {
|
|
9058
|
+
const resolved = await resolveEnvUpTree(dir, name);
|
|
9059
|
+
if (resolved === null) continue;
|
|
9060
|
+
out = out.split(`\${${name}}`).join(resolved).replace(new RegExp(`\\$${name}\\b`, "g"), resolved);
|
|
9061
|
+
}
|
|
9062
|
+
return out;
|
|
9063
|
+
}
|
|
9064
|
+
function stringLiterals(masked) {
|
|
9065
|
+
const out = [];
|
|
9066
|
+
STRING_LITERAL_RE.lastIndex = 0;
|
|
9067
|
+
let m;
|
|
9068
|
+
while ((m = STRING_LITERAL_RE.exec(masked)) !== null) out.push(m[1]);
|
|
9069
|
+
return out;
|
|
9070
|
+
}
|
|
9071
|
+
function envKeys(masked) {
|
|
9072
|
+
const out = [];
|
|
9073
|
+
ENV_READ_RE.lastIndex = 0;
|
|
9074
|
+
let m;
|
|
9075
|
+
while ((m = ENV_READ_RE.exec(masked)) !== null) {
|
|
9076
|
+
const key = m[1] ?? m[2];
|
|
9077
|
+
if (key) out.push(key);
|
|
9078
|
+
}
|
|
9079
|
+
return out;
|
|
9080
|
+
}
|
|
9081
|
+
async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
|
|
9082
|
+
const out = [];
|
|
9083
|
+
for (const key of keys) {
|
|
9084
|
+
const raw = await resolveEnvUpTree(serviceDir, key);
|
|
9085
|
+
if (raw === null) continue;
|
|
9086
|
+
const value = await interpolateEnvRefs(raw, serviceDir);
|
|
9087
|
+
if (!looksLike(value)) continue;
|
|
9088
|
+
const parsed = parse11(value);
|
|
9089
|
+
if (parsed) out.push(parsed);
|
|
9090
|
+
}
|
|
9091
|
+
for (const lit of literals) {
|
|
9092
|
+
if (!looksLike(lit)) continue;
|
|
9093
|
+
const parsed = parse11(lit);
|
|
9094
|
+
if (parsed) out.push(parsed);
|
|
9095
|
+
}
|
|
9096
|
+
return out;
|
|
9097
|
+
}
|
|
9098
|
+
async function parse9(serviceDir) {
|
|
9099
|
+
const files = (await walkSourceFiles(serviceDir).catch(() => [])).filter(
|
|
9100
|
+
(f) => path32.extname(f) === CS_EXT
|
|
9101
|
+
);
|
|
9102
|
+
if (files.length === 0) return [];
|
|
9103
|
+
const sources = [];
|
|
9104
|
+
for (const file of files) {
|
|
9105
|
+
const content = await fs22.readFile(file, "utf8").catch(() => null);
|
|
9106
|
+
if (content !== null) sources.push({ file, content });
|
|
9107
|
+
}
|
|
9108
|
+
let pgGateFile = null;
|
|
9109
|
+
let redisGateFile = null;
|
|
9110
|
+
for (const { file, content } of sources) {
|
|
9111
|
+
if (pgGateFile === null && NPGSQL_GATE.test(content)) pgGateFile = file;
|
|
9112
|
+
if (redisGateFile === null && REDIS_GATE.test(content)) redisGateFile = file;
|
|
9113
|
+
}
|
|
9114
|
+
if (!pgGateFile && !redisGateFile) return [];
|
|
9115
|
+
const literals = [];
|
|
9116
|
+
const keys = [];
|
|
9117
|
+
for (const { content } of sources) {
|
|
9118
|
+
const masked = maskCommentsInSource(content);
|
|
9119
|
+
literals.push(...stringLiterals(masked));
|
|
9120
|
+
keys.push(...envKeys(masked));
|
|
9121
|
+
}
|
|
9122
|
+
const out = [];
|
|
9123
|
+
const seenHosts = /* @__PURE__ */ new Set();
|
|
9124
|
+
const push = (config, sourceFile) => {
|
|
9125
|
+
const dedupe = `${config.engine}:${config.host}`;
|
|
9126
|
+
if (seenHosts.has(dedupe)) return;
|
|
9127
|
+
seenHosts.add(dedupe);
|
|
9128
|
+
out.push({ ...config, sourceFile });
|
|
9129
|
+
};
|
|
9130
|
+
if (pgGateFile) {
|
|
9131
|
+
for (const pg3 of await resolveConfigs(literals, keys, serviceDir, looksLikePostgres, parsePostgresConnection)) {
|
|
9132
|
+
push(pg3, pgGateFile);
|
|
9133
|
+
}
|
|
9134
|
+
}
|
|
9135
|
+
if (redisGateFile) {
|
|
9136
|
+
for (const redis of await resolveConfigs(literals, keys, serviceDir, looksLikeRedis, parseRedisEndpoint)) {
|
|
9137
|
+
push(redis, redisGateFile);
|
|
9138
|
+
}
|
|
9139
|
+
}
|
|
9140
|
+
return out;
|
|
9141
|
+
}
|
|
9142
|
+
var csharpParser = { name: "csharp", parse: parse9 };
|
|
9143
|
+
|
|
9144
|
+
// src/extract/databases/docker-compose.ts
|
|
9145
|
+
import path33 from "path";
|
|
8598
9146
|
function portFromService(svc) {
|
|
8599
9147
|
for (const raw of svc.ports ?? []) {
|
|
8600
9148
|
const str = String(raw);
|
|
@@ -8619,9 +9167,9 @@ function databaseFromEnv(svc) {
|
|
|
8619
9167
|
};
|
|
8620
9168
|
return get("POSTGRES_DB") ?? get("MYSQL_DATABASE") ?? get("MONGO_INITDB_DATABASE") ?? "";
|
|
8621
9169
|
}
|
|
8622
|
-
async function
|
|
9170
|
+
async function parse10(serviceDir) {
|
|
8623
9171
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
8624
|
-
const abs =
|
|
9172
|
+
const abs = path33.join(serviceDir, name);
|
|
8625
9173
|
if (!await exists(abs)) continue;
|
|
8626
9174
|
const raw = await readYaml(abs);
|
|
8627
9175
|
if (!raw?.services) return [];
|
|
@@ -8643,7 +9191,7 @@ async function parse9(serviceDir) {
|
|
|
8643
9191
|
}
|
|
8644
9192
|
return [];
|
|
8645
9193
|
}
|
|
8646
|
-
var dockerComposeParser = { name: "docker-compose", parse:
|
|
9194
|
+
var dockerComposeParser = { name: "docker-compose", parse: parse10 };
|
|
8647
9195
|
|
|
8648
9196
|
// src/extract/databases/index.ts
|
|
8649
9197
|
var DB_PARSERS = [
|
|
@@ -8655,6 +9203,7 @@ var DB_PARSERS = [
|
|
|
8655
9203
|
ormconfigParser,
|
|
8656
9204
|
typeormParser,
|
|
8657
9205
|
sequelizeParser,
|
|
9206
|
+
csharpParser,
|
|
8658
9207
|
dockerComposeParser
|
|
8659
9208
|
];
|
|
8660
9209
|
function compatibleDriversFor(engine) {
|
|
@@ -8793,7 +9342,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
8793
9342
|
discoveredVia: mergedDiscoveredVia
|
|
8794
9343
|
});
|
|
8795
9344
|
}
|
|
8796
|
-
const relConfigFile = toPosix(
|
|
9345
|
+
const relConfigFile = toPosix(path34.relative(service.dir, config.sourceFile));
|
|
8797
9346
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
8798
9347
|
graph,
|
|
8799
9348
|
service.pkg.name,
|
|
@@ -8802,7 +9351,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
8802
9351
|
);
|
|
8803
9352
|
nodesAdded += fn;
|
|
8804
9353
|
edgesAdded += fe;
|
|
8805
|
-
const evidenceFile = toPosix(
|
|
9354
|
+
const evidenceFile = toPosix(path34.relative(scanPath, config.sourceFile));
|
|
8806
9355
|
const edge = {
|
|
8807
9356
|
id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
|
|
8808
9357
|
source: fileNodeId,
|
|
@@ -8820,15 +9369,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
8820
9369
|
if (allConfigs.length === 1) {
|
|
8821
9370
|
const primary = allConfigs[0];
|
|
8822
9371
|
service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
|
|
8823
|
-
const relPath =
|
|
9372
|
+
const relPath = path34.relative(scanPath, primary.sourceFile);
|
|
8824
9373
|
const cfgId = configId(relPath);
|
|
8825
9374
|
if (!graph.hasNode(cfgId)) {
|
|
8826
9375
|
const cfgNode = {
|
|
8827
9376
|
id: cfgId,
|
|
8828
9377
|
type: NodeType20.ConfigNode,
|
|
8829
|
-
name:
|
|
9378
|
+
name: path34.basename(primary.sourceFile),
|
|
8830
9379
|
path: relPath,
|
|
8831
|
-
fileType: isConfigFile(
|
|
9380
|
+
fileType: isConfigFile(path34.basename(primary.sourceFile)).fileType || "config"
|
|
8832
9381
|
};
|
|
8833
9382
|
graph.addNode(cfgId, cfgNode);
|
|
8834
9383
|
nodesAdded++;
|
|
@@ -8868,8 +9417,8 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
8868
9417
|
}
|
|
8869
9418
|
|
|
8870
9419
|
// src/extract/configs.ts
|
|
8871
|
-
import { promises as
|
|
8872
|
-
import
|
|
9420
|
+
import { promises as fs23 } from "fs";
|
|
9421
|
+
import path35 from "path";
|
|
8873
9422
|
import {
|
|
8874
9423
|
EdgeType as EdgeType11,
|
|
8875
9424
|
NodeType as NodeType21,
|
|
@@ -8878,23 +9427,23 @@ import {
|
|
|
8878
9427
|
confidenceForExtracted as confidenceForExtracted8
|
|
8879
9428
|
} from "@neat.is/types";
|
|
8880
9429
|
async function walkConfigFiles(dir, excludeDirs = []) {
|
|
8881
|
-
const excluded = new Set(excludeDirs.map((d) =>
|
|
9430
|
+
const excluded = new Set(excludeDirs.map((d) => path35.resolve(d)));
|
|
8882
9431
|
const out = [];
|
|
8883
|
-
async function
|
|
8884
|
-
const entries = await
|
|
9432
|
+
async function walk10(current) {
|
|
9433
|
+
const entries = await fs23.readdir(current, { withFileTypes: true });
|
|
8885
9434
|
for (const entry of entries) {
|
|
8886
|
-
const full =
|
|
9435
|
+
const full = path35.join(current, entry.name);
|
|
8887
9436
|
if (entry.isDirectory()) {
|
|
8888
9437
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
8889
|
-
if (excluded.has(
|
|
9438
|
+
if (excluded.has(path35.resolve(full))) continue;
|
|
8890
9439
|
if (await isPythonVenvDir(full)) continue;
|
|
8891
|
-
await
|
|
9440
|
+
await walk10(full);
|
|
8892
9441
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
8893
9442
|
out.push(full);
|
|
8894
9443
|
}
|
|
8895
9444
|
}
|
|
8896
9445
|
}
|
|
8897
|
-
await
|
|
9446
|
+
await walk10(dir);
|
|
8898
9447
|
return out;
|
|
8899
9448
|
}
|
|
8900
9449
|
async function addConfigNodes(graph, services, scanPath) {
|
|
@@ -8903,19 +9452,19 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
8903
9452
|
for (const service of services) {
|
|
8904
9453
|
const configFiles = await walkConfigFiles(service.dir, service.excludeDirs);
|
|
8905
9454
|
for (const file of configFiles) {
|
|
8906
|
-
const relPath =
|
|
9455
|
+
const relPath = path35.relative(scanPath, file);
|
|
8907
9456
|
const node = {
|
|
8908
9457
|
id: configId2(relPath),
|
|
8909
9458
|
type: NodeType21.ConfigNode,
|
|
8910
|
-
name:
|
|
9459
|
+
name: path35.basename(file),
|
|
8911
9460
|
path: relPath,
|
|
8912
|
-
fileType: isConfigFile(
|
|
9461
|
+
fileType: isConfigFile(path35.basename(file)).fileType
|
|
8913
9462
|
};
|
|
8914
9463
|
if (!graph.hasNode(node.id)) {
|
|
8915
9464
|
graph.addNode(node.id, node);
|
|
8916
9465
|
nodesAdded++;
|
|
8917
9466
|
}
|
|
8918
|
-
const relToService = toPosix(
|
|
9467
|
+
const relToService = toPosix(path35.relative(service.dir, file));
|
|
8919
9468
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
8920
9469
|
graph,
|
|
8921
9470
|
service.pkg.name,
|
|
@@ -8931,7 +9480,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
8931
9480
|
type: EdgeType11.CONFIGURED_BY,
|
|
8932
9481
|
provenance: Provenance11.EXTRACTED,
|
|
8933
9482
|
confidence: confidenceForExtracted8("structural"),
|
|
8934
|
-
evidence: { file: relPath.split(
|
|
9483
|
+
evidence: { file: relPath.split(path35.sep).join("/") }
|
|
8935
9484
|
};
|
|
8936
9485
|
if (!graph.hasEdge(edge.id)) {
|
|
8937
9486
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -8943,8 +9492,8 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
8943
9492
|
}
|
|
8944
9493
|
|
|
8945
9494
|
// src/extract/proto.ts
|
|
8946
|
-
import { promises as
|
|
8947
|
-
import
|
|
9495
|
+
import { promises as fs24 } from "fs";
|
|
9496
|
+
import path36 from "path";
|
|
8948
9497
|
import {
|
|
8949
9498
|
EdgeType as EdgeType12,
|
|
8950
9499
|
NodeType as NodeType22,
|
|
@@ -8989,23 +9538,23 @@ function grpcMethodsFromProto(content, fqPackage) {
|
|
|
8989
9538
|
return out;
|
|
8990
9539
|
}
|
|
8991
9540
|
async function walkProtoFiles(dir, excludeDirs = []) {
|
|
8992
|
-
const excluded = new Set(excludeDirs.map((d) =>
|
|
9541
|
+
const excluded = new Set(excludeDirs.map((d) => path36.resolve(d)));
|
|
8993
9542
|
const out = [];
|
|
8994
|
-
async function
|
|
8995
|
-
const entries = await
|
|
9543
|
+
async function walk10(current) {
|
|
9544
|
+
const entries = await fs24.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
8996
9545
|
for (const entry of entries) {
|
|
8997
|
-
const full =
|
|
9546
|
+
const full = path36.join(current, entry.name);
|
|
8998
9547
|
if (entry.isDirectory()) {
|
|
8999
9548
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
9000
|
-
if (excluded.has(
|
|
9549
|
+
if (excluded.has(path36.resolve(full))) continue;
|
|
9001
9550
|
if (await isPythonVenvDir(full)) continue;
|
|
9002
|
-
await
|
|
9003
|
-
} else if (entry.isFile() &&
|
|
9551
|
+
await walk10(full);
|
|
9552
|
+
} else if (entry.isFile() && path36.extname(entry.name) === PROTO_EXTENSION) {
|
|
9004
9553
|
out.push(full);
|
|
9005
9554
|
}
|
|
9006
9555
|
}
|
|
9007
9556
|
}
|
|
9008
|
-
await
|
|
9557
|
+
await walk10(dir);
|
|
9009
9558
|
return out;
|
|
9010
9559
|
}
|
|
9011
9560
|
async function addGrpcMethods(graph, services) {
|
|
@@ -9015,10 +9564,10 @@ async function addGrpcMethods(graph, services) {
|
|
|
9015
9564
|
const protoPaths = await walkProtoFiles(service.dir, service.excludeDirs);
|
|
9016
9565
|
for (const protoPath of protoPaths) {
|
|
9017
9566
|
if (isTestPath(protoPath)) continue;
|
|
9018
|
-
const relFile = toPosix(
|
|
9567
|
+
const relFile = toPosix(path36.relative(service.dir, protoPath));
|
|
9019
9568
|
let content;
|
|
9020
9569
|
try {
|
|
9021
|
-
content = await
|
|
9570
|
+
content = await fs24.readFile(protoPath, "utf8");
|
|
9022
9571
|
} catch (err) {
|
|
9023
9572
|
recordExtractionError("proto extraction", protoPath, err);
|
|
9024
9573
|
continue;
|
|
@@ -9081,7 +9630,7 @@ import {
|
|
|
9081
9630
|
} from "@neat.is/types";
|
|
9082
9631
|
|
|
9083
9632
|
// src/extract/calls/http.ts
|
|
9084
|
-
import
|
|
9633
|
+
import path37 from "path";
|
|
9085
9634
|
import Parser6 from "tree-sitter";
|
|
9086
9635
|
import JavaScript4 from "tree-sitter-javascript";
|
|
9087
9636
|
import TypeScript2 from "tree-sitter-typescript";
|
|
@@ -9169,7 +9718,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
9169
9718
|
const seen = /* @__PURE__ */ new Set();
|
|
9170
9719
|
for (const file of files) {
|
|
9171
9720
|
if (isTestPath(file.path)) continue;
|
|
9172
|
-
const parser = parserForExt(
|
|
9721
|
+
const parser = parserForExt(path37.extname(file.path), parserCache);
|
|
9173
9722
|
let sites;
|
|
9174
9723
|
try {
|
|
9175
9724
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -9178,7 +9727,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
9178
9727
|
continue;
|
|
9179
9728
|
}
|
|
9180
9729
|
if (sites.length === 0) continue;
|
|
9181
|
-
const relFile = toPosix(
|
|
9730
|
+
const relFile = toPosix(path37.relative(service.dir, file.path));
|
|
9182
9731
|
for (const site of sites) {
|
|
9183
9732
|
const targetId = hostToNodeId.get(site.host);
|
|
9184
9733
|
if (!targetId || targetId === service.node.id) continue;
|
|
@@ -9231,7 +9780,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
9231
9780
|
}
|
|
9232
9781
|
|
|
9233
9782
|
// src/extract/calls/route-match.ts
|
|
9234
|
-
import
|
|
9783
|
+
import path38 from "path";
|
|
9235
9784
|
import Parser7 from "tree-sitter";
|
|
9236
9785
|
import JavaScript5 from "tree-sitter-javascript";
|
|
9237
9786
|
import {
|
|
@@ -9437,7 +9986,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
9437
9986
|
const seen = /* @__PURE__ */ new Set();
|
|
9438
9987
|
for (const file of files) {
|
|
9439
9988
|
if (isTestPath(file.path)) continue;
|
|
9440
|
-
if (!JS_CLIENT_EXTENSIONS.has(
|
|
9989
|
+
if (!JS_CLIENT_EXTENSIONS.has(path38.extname(file.path))) continue;
|
|
9441
9990
|
let sites;
|
|
9442
9991
|
try {
|
|
9443
9992
|
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
@@ -9446,7 +9995,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
9446
9995
|
continue;
|
|
9447
9996
|
}
|
|
9448
9997
|
if (sites.length === 0) continue;
|
|
9449
|
-
const relFile = toPosix(
|
|
9998
|
+
const relFile = toPosix(path38.relative(service.dir, file.path));
|
|
9450
9999
|
for (const site of sites) {
|
|
9451
10000
|
const serverServiceId = hostToNodeId.get(site.host);
|
|
9452
10001
|
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
@@ -9506,7 +10055,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
9506
10055
|
}
|
|
9507
10056
|
|
|
9508
10057
|
// src/extract/calls/kafka.ts
|
|
9509
|
-
import
|
|
10058
|
+
import path39 from "path";
|
|
9510
10059
|
import { infraId as infraId2 } from "@neat.is/types";
|
|
9511
10060
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
9512
10061
|
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 +10143,13 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
9594
10143
|
// call sites — verified-call-site tier (ADR-066).
|
|
9595
10144
|
confidenceKind: "verified-call-site",
|
|
9596
10145
|
evidence: {
|
|
9597
|
-
file:
|
|
10146
|
+
file: path39.relative(serviceDir, file.path),
|
|
9598
10147
|
line,
|
|
9599
10148
|
snippet: snippet(file.content, line)
|
|
9600
10149
|
}
|
|
9601
10150
|
});
|
|
9602
10151
|
};
|
|
9603
|
-
if (
|
|
10152
|
+
if (path39.extname(file.path) === ".go") {
|
|
9604
10153
|
goSaramaEndpoints(file.content, make);
|
|
9605
10154
|
} else {
|
|
9606
10155
|
for (const { topic } of findAll(PRODUCER_TOPIC_RE, file.content)) make(topic, "PUBLISHES_TO");
|
|
@@ -9610,7 +10159,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
9610
10159
|
}
|
|
9611
10160
|
|
|
9612
10161
|
// src/extract/calls/redis.ts
|
|
9613
|
-
import
|
|
10162
|
+
import path40 from "path";
|
|
9614
10163
|
import { infraId as infraId3 } from "@neat.is/types";
|
|
9615
10164
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
9616
10165
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -9633,7 +10182,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
9633
10182
|
// support tier (ADR-066).
|
|
9634
10183
|
confidenceKind: "url-with-structural-support",
|
|
9635
10184
|
evidence: {
|
|
9636
|
-
file:
|
|
10185
|
+
file: path40.relative(serviceDir, file.path),
|
|
9637
10186
|
line,
|
|
9638
10187
|
snippet: snippet(file.content, line)
|
|
9639
10188
|
}
|
|
@@ -9643,7 +10192,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
9643
10192
|
}
|
|
9644
10193
|
|
|
9645
10194
|
// src/extract/calls/aws.ts
|
|
9646
|
-
import
|
|
10195
|
+
import path41 from "path";
|
|
9647
10196
|
import { infraId as infraId4 } from "@neat.is/types";
|
|
9648
10197
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
9649
10198
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -9677,7 +10226,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
9677
10226
|
// (ADR-066).
|
|
9678
10227
|
confidenceKind: "verified-call-site",
|
|
9679
10228
|
evidence: {
|
|
9680
|
-
file:
|
|
10229
|
+
file: path41.relative(serviceDir, file.path),
|
|
9681
10230
|
line,
|
|
9682
10231
|
snippet: snippet(file.content, line)
|
|
9683
10232
|
}
|
|
@@ -9701,7 +10250,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
9701
10250
|
}
|
|
9702
10251
|
|
|
9703
10252
|
// src/extract/calls/grpc.ts
|
|
9704
|
-
import
|
|
10253
|
+
import path42 from "path";
|
|
9705
10254
|
import { infraId as infraId5 } from "@neat.is/types";
|
|
9706
10255
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
9707
10256
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
@@ -9760,7 +10309,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
9760
10309
|
// tier (ADR-066).
|
|
9761
10310
|
confidenceKind: "verified-call-site",
|
|
9762
10311
|
evidence: {
|
|
9763
|
-
file:
|
|
10312
|
+
file: path42.relative(serviceDir, file.path),
|
|
9764
10313
|
line,
|
|
9765
10314
|
snippet: snippet(file.content, line)
|
|
9766
10315
|
}
|
|
@@ -9770,7 +10319,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
9770
10319
|
}
|
|
9771
10320
|
|
|
9772
10321
|
// src/extract/calls/supabase.ts
|
|
9773
|
-
import
|
|
10322
|
+
import path43 from "path";
|
|
9774
10323
|
import { infraId as infraId6 } from "@neat.is/types";
|
|
9775
10324
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
9776
10325
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
@@ -9829,7 +10378,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
9829
10378
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
9830
10379
|
confidenceKind: "verified-call-site",
|
|
9831
10380
|
evidence: {
|
|
9832
|
-
file:
|
|
10381
|
+
file: path43.relative(serviceDir, file.path),
|
|
9833
10382
|
line,
|
|
9834
10383
|
snippet: snippet(file.content, line)
|
|
9835
10384
|
}
|
|
@@ -9856,7 +10405,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
9856
10405
|
edgeType: "CALLS",
|
|
9857
10406
|
confidenceKind: "verified-call-site",
|
|
9858
10407
|
evidence: {
|
|
9859
|
-
file:
|
|
10408
|
+
file: path43.relative(serviceDir, file.path),
|
|
9860
10409
|
line,
|
|
9861
10410
|
snippet: snippet(file.content, line)
|
|
9862
10411
|
}
|
|
@@ -9867,7 +10416,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
9867
10416
|
}
|
|
9868
10417
|
|
|
9869
10418
|
// src/extract/calls/firestore.ts
|
|
9870
|
-
import
|
|
10419
|
+
import path44 from "path";
|
|
9871
10420
|
import Parser8 from "tree-sitter";
|
|
9872
10421
|
import JavaScript6 from "tree-sitter-javascript";
|
|
9873
10422
|
import { infraId as infraId7 } from "@neat.is/types";
|
|
@@ -9906,7 +10455,7 @@ function isFirestoreClientFactory(node) {
|
|
|
9906
10455
|
}
|
|
9907
10456
|
function firestoreClientVars(root) {
|
|
9908
10457
|
const vars = /* @__PURE__ */ new Set();
|
|
9909
|
-
const
|
|
10458
|
+
const walk10 = (node) => {
|
|
9910
10459
|
if (node.type === "variable_declarator") {
|
|
9911
10460
|
const name = node.childForFieldName("name");
|
|
9912
10461
|
let value = node.childForFieldName("value");
|
|
@@ -9915,9 +10464,9 @@ function firestoreClientVars(root) {
|
|
|
9915
10464
|
vars.add(name.text);
|
|
9916
10465
|
}
|
|
9917
10466
|
}
|
|
9918
|
-
for (const c of namedChildren(node))
|
|
10467
|
+
for (const c of namedChildren(node)) walk10(c);
|
|
9919
10468
|
};
|
|
9920
|
-
|
|
10469
|
+
walk10(root);
|
|
9921
10470
|
return vars;
|
|
9922
10471
|
}
|
|
9923
10472
|
function isClientExpr(node, clientVars) {
|
|
@@ -10038,7 +10587,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10038
10587
|
const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
|
|
10039
10588
|
if (!hasClient && !hasAdmin) return [];
|
|
10040
10589
|
const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
10041
|
-
const tree = parseSource3(parserForExt2(
|
|
10590
|
+
const tree = parseSource3(parserForExt2(path44.extname(file.path)), file.content);
|
|
10042
10591
|
const clientVars = firestoreClientVars(tree.rootNode);
|
|
10043
10592
|
const collLine = /* @__PURE__ */ new Map();
|
|
10044
10593
|
const writes = /* @__PURE__ */ new Map();
|
|
@@ -10072,7 +10621,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10072
10621
|
}
|
|
10073
10622
|
s.add(field);
|
|
10074
10623
|
};
|
|
10075
|
-
const
|
|
10624
|
+
const walk10 = (node) => {
|
|
10076
10625
|
if (node.type === "call_expression") {
|
|
10077
10626
|
const fn = node.childForFieldName("function");
|
|
10078
10627
|
const line = node.startPosition.row + 1;
|
|
@@ -10112,9 +10661,9 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10112
10661
|
}
|
|
10113
10662
|
}
|
|
10114
10663
|
}
|
|
10115
|
-
for (const c of namedChildren(node))
|
|
10664
|
+
for (const c of namedChildren(node)) walk10(c);
|
|
10116
10665
|
};
|
|
10117
|
-
|
|
10666
|
+
walk10(tree.rootNode);
|
|
10118
10667
|
const out = [];
|
|
10119
10668
|
for (const [collPath, line] of collLine) {
|
|
10120
10669
|
const byField = writes.get(collPath);
|
|
@@ -10142,7 +10691,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10142
10691
|
...columnSet.size > 0 ? { columns: [...columnSet] } : {},
|
|
10143
10692
|
...sdkWrites ? { sdkWrites } : {},
|
|
10144
10693
|
evidence: {
|
|
10145
|
-
file:
|
|
10694
|
+
file: path44.relative(serviceDir, file.path),
|
|
10146
10695
|
line,
|
|
10147
10696
|
snippet: snippet(file.content, line)
|
|
10148
10697
|
}
|
|
@@ -10152,7 +10701,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10152
10701
|
}
|
|
10153
10702
|
|
|
10154
10703
|
// src/extract/calls/mongoose.ts
|
|
10155
|
-
import
|
|
10704
|
+
import path45 from "path";
|
|
10156
10705
|
import { infraId as infraId8 } from "@neat.is/types";
|
|
10157
10706
|
var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
|
|
10158
10707
|
var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
|
|
@@ -10303,7 +10852,7 @@ function endpoint(r, file, serviceDir, matchText) {
|
|
|
10303
10852
|
kind: r.kind,
|
|
10304
10853
|
edgeType: "CALLS",
|
|
10305
10854
|
confidenceKind: "verified-call-site",
|
|
10306
|
-
evidence: { file:
|
|
10855
|
+
evidence: { file: path45.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
10307
10856
|
};
|
|
10308
10857
|
}
|
|
10309
10858
|
function mongooseEndpointsFromFile(file, serviceDir) {
|
|
@@ -10393,7 +10942,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
10393
10942
|
const registry = /* @__PURE__ */ new Map();
|
|
10394
10943
|
for (const f of mongooseFiles) {
|
|
10395
10944
|
const fx = fileExportsOf(f.content, pluralizeOn);
|
|
10396
|
-
if (fx) registry.set(toPosix(
|
|
10945
|
+
if (fx) registry.set(toPosix(path45.relative(serviceDir, f.path)), fx);
|
|
10397
10946
|
}
|
|
10398
10947
|
if (registry.size === 0) return [];
|
|
10399
10948
|
const out = [];
|
|
@@ -10404,7 +10953,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
10404
10953
|
const directColl = /* @__PURE__ */ new Map();
|
|
10405
10954
|
const nsExports = /* @__PURE__ */ new Map();
|
|
10406
10955
|
for (const b of bindings) {
|
|
10407
|
-
const resolvedRel = await resolveJsImport(b.specifier,
|
|
10956
|
+
const resolvedRel = await resolveJsImport(b.specifier, path45.dirname(f.path), serviceDir, null);
|
|
10408
10957
|
if (!resolvedRel) continue;
|
|
10409
10958
|
const fx = registry.get(resolvedRel);
|
|
10410
10959
|
if (!fx) continue;
|
|
@@ -10447,7 +10996,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
10447
10996
|
}
|
|
10448
10997
|
|
|
10449
10998
|
// src/extract/calls/sqlalchemy.ts
|
|
10450
|
-
import
|
|
10999
|
+
import path46 from "path";
|
|
10451
11000
|
import Parser9 from "tree-sitter";
|
|
10452
11001
|
import Python5 from "tree-sitter-python";
|
|
10453
11002
|
import { infraId as infraId9 } from "@neat.is/types";
|
|
@@ -10586,7 +11135,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
|
|
|
10586
11135
|
childTable,
|
|
10587
11136
|
parentTable,
|
|
10588
11137
|
evidence: {
|
|
10589
|
-
file:
|
|
11138
|
+
file: path46.relative(serviceDir, file.path),
|
|
10590
11139
|
line,
|
|
10591
11140
|
snippet: snippet(file.content, line)
|
|
10592
11141
|
}
|
|
@@ -10611,7 +11160,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
|
10611
11160
|
confidenceKind: "verified-call-site",
|
|
10612
11161
|
...columns && columns.length > 0 ? { columns } : {},
|
|
10613
11162
|
evidence: {
|
|
10614
|
-
file:
|
|
11163
|
+
file: path46.relative(serviceDir, file.path),
|
|
10615
11164
|
line,
|
|
10616
11165
|
snippet: snippet(file.content, line)
|
|
10617
11166
|
}
|
|
@@ -10718,7 +11267,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
10718
11267
|
edgeType: "CALLS",
|
|
10719
11268
|
confidenceKind: "verified-call-site",
|
|
10720
11269
|
evidence: {
|
|
10721
|
-
file:
|
|
11270
|
+
file: path46.relative(serviceDir, file.path),
|
|
10722
11271
|
line,
|
|
10723
11272
|
snippet: snippet(file.content, line)
|
|
10724
11273
|
}
|
|
@@ -10729,7 +11278,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
10729
11278
|
}
|
|
10730
11279
|
|
|
10731
11280
|
// src/extract/calls/django-orm.ts
|
|
10732
|
-
import
|
|
11281
|
+
import path47 from "path";
|
|
10733
11282
|
import Parser10 from "tree-sitter";
|
|
10734
11283
|
import Python6 from "tree-sitter-python";
|
|
10735
11284
|
import { infraId as infraId10 } from "@neat.is/types";
|
|
@@ -10799,7 +11348,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
10799
11348
|
const tree = parseSource7(makePyParser4(), file.content);
|
|
10800
11349
|
const out = [];
|
|
10801
11350
|
const seen = /* @__PURE__ */ new Set();
|
|
10802
|
-
const defaultAppLabel =
|
|
11351
|
+
const defaultAppLabel = path47.basename(path47.dirname(file.path));
|
|
10803
11352
|
walk4(tree.rootNode, (node) => {
|
|
10804
11353
|
if (node.type !== "class_definition") return;
|
|
10805
11354
|
if (!extendsDjangoModel(node)) return;
|
|
@@ -10817,14 +11366,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
10817
11366
|
kind: "sql-table",
|
|
10818
11367
|
edgeType: "CALLS",
|
|
10819
11368
|
confidenceKind: "verified-call-site",
|
|
10820
|
-
evidence: { file:
|
|
11369
|
+
evidence: { file: path47.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
10821
11370
|
});
|
|
10822
11371
|
});
|
|
10823
11372
|
return out;
|
|
10824
11373
|
}
|
|
10825
11374
|
|
|
10826
11375
|
// src/extract/calls/drizzle.ts
|
|
10827
|
-
import
|
|
11376
|
+
import path48 from "path";
|
|
10828
11377
|
import Parser11 from "tree-sitter";
|
|
10829
11378
|
import JavaScript7 from "tree-sitter-javascript";
|
|
10830
11379
|
import { infraId as infraId11 } from "@neat.is/types";
|
|
@@ -10902,10 +11451,10 @@ function columnsFromObject(obj) {
|
|
|
10902
11451
|
}
|
|
10903
11452
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
10904
11453
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10905
|
-
const tree = parseSource3(parserForExt3(
|
|
11454
|
+
const tree = parseSource3(parserForExt3(path48.extname(file.path)), file.content);
|
|
10906
11455
|
const out = [];
|
|
10907
11456
|
const seen = /* @__PURE__ */ new Set();
|
|
10908
|
-
const
|
|
11457
|
+
const walk10 = (node) => {
|
|
10909
11458
|
if (node.type === "call_expression") {
|
|
10910
11459
|
const fn = node.childForFieldName("function");
|
|
10911
11460
|
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
@@ -10925,7 +11474,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
10925
11474
|
confidenceKind: "structural",
|
|
10926
11475
|
columns,
|
|
10927
11476
|
evidence: {
|
|
10928
|
-
file:
|
|
11477
|
+
file: path48.relative(serviceDir, file.path),
|
|
10929
11478
|
line,
|
|
10930
11479
|
snippet: snippet(file.content, line)
|
|
10931
11480
|
}
|
|
@@ -10933,9 +11482,9 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
10933
11482
|
}
|
|
10934
11483
|
}
|
|
10935
11484
|
}
|
|
10936
|
-
for (const c of namedChildren4(node))
|
|
11485
|
+
for (const c of namedChildren4(node)) walk10(c);
|
|
10937
11486
|
};
|
|
10938
|
-
|
|
11487
|
+
walk10(tree.rootNode);
|
|
10939
11488
|
return out;
|
|
10940
11489
|
}
|
|
10941
11490
|
function enclosingVarName(call) {
|
|
@@ -10957,7 +11506,7 @@ function enclosingVarName(call) {
|
|
|
10957
11506
|
function collectDrizzleTables(root) {
|
|
10958
11507
|
const tables = [];
|
|
10959
11508
|
const varToTable = /* @__PURE__ */ new Map();
|
|
10960
|
-
const
|
|
11509
|
+
const walk10 = (node) => {
|
|
10961
11510
|
if (node.type === "call_expression") {
|
|
10962
11511
|
const fn = node.childForFieldName("function");
|
|
10963
11512
|
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
@@ -10972,9 +11521,9 @@ function collectDrizzleTables(root) {
|
|
|
10972
11521
|
}
|
|
10973
11522
|
}
|
|
10974
11523
|
}
|
|
10975
|
-
for (const c of namedChildren4(node))
|
|
11524
|
+
for (const c of namedChildren4(node)) walk10(c);
|
|
10976
11525
|
};
|
|
10977
|
-
|
|
11526
|
+
walk10(root);
|
|
10978
11527
|
return { tables, varToTable };
|
|
10979
11528
|
}
|
|
10980
11529
|
function referencesTargetVar(call) {
|
|
@@ -10991,13 +11540,13 @@ function referencesTargetVar(call) {
|
|
|
10991
11540
|
}
|
|
10992
11541
|
function drizzleForeignKeys(file, serviceDir) {
|
|
10993
11542
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10994
|
-
const tree = parseSource3(parserForExt3(
|
|
11543
|
+
const tree = parseSource3(parserForExt3(path48.extname(file.path)), file.content);
|
|
10995
11544
|
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
10996
11545
|
const out = [];
|
|
10997
11546
|
const seen = /* @__PURE__ */ new Set();
|
|
10998
11547
|
for (const table of tables) {
|
|
10999
11548
|
if (!table.object) continue;
|
|
11000
|
-
const
|
|
11549
|
+
const walk10 = (node) => {
|
|
11001
11550
|
if (node.type === "call_expression") {
|
|
11002
11551
|
const targetVar = referencesTargetVar(node);
|
|
11003
11552
|
const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
|
|
@@ -11010,7 +11559,7 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
11010
11559
|
childTable: table.tableName,
|
|
11011
11560
|
parentTable,
|
|
11012
11561
|
evidence: {
|
|
11013
|
-
file:
|
|
11562
|
+
file: path48.relative(serviceDir, file.path),
|
|
11014
11563
|
line,
|
|
11015
11564
|
snippet: snippet(file.content, line)
|
|
11016
11565
|
}
|
|
@@ -11018,15 +11567,15 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
11018
11567
|
}
|
|
11019
11568
|
}
|
|
11020
11569
|
}
|
|
11021
|
-
for (const c of namedChildren4(node))
|
|
11570
|
+
for (const c of namedChildren4(node)) walk10(c);
|
|
11022
11571
|
};
|
|
11023
|
-
|
|
11572
|
+
walk10(table.object);
|
|
11024
11573
|
}
|
|
11025
11574
|
return out;
|
|
11026
11575
|
}
|
|
11027
11576
|
|
|
11028
11577
|
// src/extract/calls/prisma.ts
|
|
11029
|
-
import
|
|
11578
|
+
import path49 from "path";
|
|
11030
11579
|
import { infraId as infraId12 } from "@neat.is/types";
|
|
11031
11580
|
var SCALAR_TYPES = /* @__PURE__ */ new Set([
|
|
11032
11581
|
"Int",
|
|
@@ -11082,7 +11631,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
11082
11631
|
confidenceKind: "structural",
|
|
11083
11632
|
columns: b.columns,
|
|
11084
11633
|
evidence: {
|
|
11085
|
-
file:
|
|
11634
|
+
file: path49.relative(serviceDir, file.path),
|
|
11086
11635
|
line: b.startLine,
|
|
11087
11636
|
snippet: snippet(content, b.startLine)
|
|
11088
11637
|
}
|
|
@@ -11143,7 +11692,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
11143
11692
|
}
|
|
11144
11693
|
async function prismaColumnEndpoints(serviceDir) {
|
|
11145
11694
|
const schemaPath = await findFirst(serviceDir, [
|
|
11146
|
-
|
|
11695
|
+
path49.join("prisma", "schema.prisma"),
|
|
11147
11696
|
"schema.prisma"
|
|
11148
11697
|
]);
|
|
11149
11698
|
if (!schemaPath) return [];
|
|
@@ -11218,7 +11767,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
11218
11767
|
childTable: current.table,
|
|
11219
11768
|
parentTable,
|
|
11220
11769
|
evidence: {
|
|
11221
|
-
file:
|
|
11770
|
+
file: path49.relative(serviceDir, file.path),
|
|
11222
11771
|
line: lineNo,
|
|
11223
11772
|
snippet: snippet(content, lineNo)
|
|
11224
11773
|
}
|
|
@@ -11233,7 +11782,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
11233
11782
|
}
|
|
11234
11783
|
async function prismaForeignKeys(serviceDir) {
|
|
11235
11784
|
const schemaPath = await findFirst(serviceDir, [
|
|
11236
|
-
|
|
11785
|
+
path49.join("prisma", "schema.prisma"),
|
|
11237
11786
|
"schema.prisma"
|
|
11238
11787
|
]);
|
|
11239
11788
|
if (!schemaPath) return [];
|
|
@@ -11243,7 +11792,7 @@ async function prismaForeignKeys(serviceDir) {
|
|
|
11243
11792
|
}
|
|
11244
11793
|
|
|
11245
11794
|
// src/extract/calls/activerecord.ts
|
|
11246
|
-
import
|
|
11795
|
+
import path50 from "path";
|
|
11247
11796
|
import Parser12 from "tree-sitter";
|
|
11248
11797
|
import Ruby3 from "tree-sitter-ruby";
|
|
11249
11798
|
import { infraId as infraId13 } from "@neat.is/types";
|
|
@@ -11451,7 +12000,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
|
|
|
11451
12000
|
confidenceKind: "structural",
|
|
11452
12001
|
...table.columns.length > 0 ? { columns: table.columns } : {},
|
|
11453
12002
|
evidence: {
|
|
11454
|
-
file:
|
|
12003
|
+
file: path50.relative(serviceDir, file.path),
|
|
11455
12004
|
line: table.line,
|
|
11456
12005
|
snippet: snippet(file.content, table.line)
|
|
11457
12006
|
}
|
|
@@ -11473,7 +12022,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
|
|
|
11473
12022
|
childTable,
|
|
11474
12023
|
parentTable,
|
|
11475
12024
|
evidence: {
|
|
11476
|
-
file:
|
|
12025
|
+
file: path50.relative(serviceDir, file.path),
|
|
11477
12026
|
line,
|
|
11478
12027
|
snippet: snippet(file.content, line)
|
|
11479
12028
|
}
|
|
@@ -11556,7 +12105,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
|
|
|
11556
12105
|
edgeType: "CALLS",
|
|
11557
12106
|
confidenceKind: "verified-call-site",
|
|
11558
12107
|
evidence: {
|
|
11559
|
-
file:
|
|
12108
|
+
file: path50.relative(serviceDir, file.path),
|
|
11560
12109
|
line,
|
|
11561
12110
|
snippet: snippet(file.content, line)
|
|
11562
12111
|
}
|
|
@@ -11593,7 +12142,7 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
11593
12142
|
childTable,
|
|
11594
12143
|
parentTable,
|
|
11595
12144
|
evidence: {
|
|
11596
|
-
file:
|
|
12145
|
+
file: path50.relative(serviceDir, file.path),
|
|
11597
12146
|
line,
|
|
11598
12147
|
snippet: snippet(file.content, line)
|
|
11599
12148
|
}
|
|
@@ -11604,7 +12153,7 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
11604
12153
|
}
|
|
11605
12154
|
|
|
11606
12155
|
// src/extract/calls/eloquent.ts
|
|
11607
|
-
import
|
|
12156
|
+
import path51 from "path";
|
|
11608
12157
|
import Parser13 from "tree-sitter";
|
|
11609
12158
|
import Php3 from "tree-sitter-php";
|
|
11610
12159
|
import { infraId as infraId14 } from "@neat.is/types";
|
|
@@ -11861,7 +12410,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
|
|
|
11861
12410
|
confidenceKind: "structural",
|
|
11862
12411
|
...columns.length > 0 ? { columns } : {},
|
|
11863
12412
|
evidence: {
|
|
11864
|
-
file:
|
|
12413
|
+
file: path51.relative(serviceDir, file.path),
|
|
11865
12414
|
line: bp.line,
|
|
11866
12415
|
snippet: snippet(file.content, bp.line)
|
|
11867
12416
|
}
|
|
@@ -11883,7 +12432,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
|
|
|
11883
12432
|
childTable,
|
|
11884
12433
|
parentTable,
|
|
11885
12434
|
evidence: {
|
|
11886
|
-
file:
|
|
12435
|
+
file: path51.relative(serviceDir, file.path),
|
|
11887
12436
|
line,
|
|
11888
12437
|
snippet: snippet(file.content, line)
|
|
11889
12438
|
}
|
|
@@ -12003,7 +12552,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
|
|
|
12003
12552
|
edgeType: "CALLS",
|
|
12004
12553
|
confidenceKind: "verified-call-site",
|
|
12005
12554
|
evidence: {
|
|
12006
|
-
file:
|
|
12555
|
+
file: path51.relative(serviceDir, file.path),
|
|
12007
12556
|
line,
|
|
12008
12557
|
snippet: snippet(file.content, line)
|
|
12009
12558
|
}
|
|
@@ -12039,7 +12588,7 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
12039
12588
|
childTable,
|
|
12040
12589
|
parentTable,
|
|
12041
12590
|
evidence: {
|
|
12042
|
-
file:
|
|
12591
|
+
file: path51.relative(serviceDir, file.path),
|
|
12043
12592
|
line,
|
|
12044
12593
|
snippet: snippet(file.content, line)
|
|
12045
12594
|
}
|
|
@@ -12050,7 +12599,7 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
12050
12599
|
}
|
|
12051
12600
|
|
|
12052
12601
|
// src/extract/calls/go.ts
|
|
12053
|
-
import
|
|
12602
|
+
import path52 from "path";
|
|
12054
12603
|
import Parser14 from "tree-sitter";
|
|
12055
12604
|
import Go4 from "tree-sitter-go";
|
|
12056
12605
|
import { infraId as infraId15 } from "@neat.is/types";
|
|
@@ -12124,7 +12673,7 @@ function firstStringLiteralArg(argsNode) {
|
|
|
12124
12673
|
return null;
|
|
12125
12674
|
}
|
|
12126
12675
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
12127
|
-
if (
|
|
12676
|
+
if (path52.extname(file.path) !== ".go") return [];
|
|
12128
12677
|
if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
|
|
12129
12678
|
const tree = parseSource10(makeGoParser3(), file.content);
|
|
12130
12679
|
const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
|
|
@@ -12153,7 +12702,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
12153
12702
|
confidenceKind: "verified-call-site",
|
|
12154
12703
|
...columns.length > 0 ? { columns } : {},
|
|
12155
12704
|
evidence: {
|
|
12156
|
-
file: toPosix(
|
|
12705
|
+
file: toPosix(path52.relative(serviceDir, file.path)),
|
|
12157
12706
|
line,
|
|
12158
12707
|
snippet: snippet(file.content, line)
|
|
12159
12708
|
}
|
|
@@ -12163,7 +12712,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
12163
12712
|
}
|
|
12164
12713
|
|
|
12165
12714
|
// src/extract/calls/gorm.ts
|
|
12166
|
-
import
|
|
12715
|
+
import path53 from "path";
|
|
12167
12716
|
import Parser15 from "tree-sitter";
|
|
12168
12717
|
import Go5 from "tree-sitter-go";
|
|
12169
12718
|
import { infraId as infraId16 } from "@neat.is/types";
|
|
@@ -12596,7 +13145,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
12596
13145
|
seen.delete(struct.name);
|
|
12597
13146
|
}
|
|
12598
13147
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
12599
|
-
if (
|
|
13148
|
+
if (path53.extname(file.path) !== ".go") return [];
|
|
12600
13149
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
12601
13150
|
const tree = parseSource11(makeGoParser4(), file.content);
|
|
12602
13151
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -12618,7 +13167,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
12618
13167
|
confidenceKind: "structural",
|
|
12619
13168
|
...columns.length > 0 ? { columns } : {},
|
|
12620
13169
|
evidence: {
|
|
12621
|
-
file: toPosix(
|
|
13170
|
+
file: toPosix(path53.relative(serviceDir, file.path)),
|
|
12622
13171
|
line: struct.line,
|
|
12623
13172
|
snippet: snippet(file.content, struct.line)
|
|
12624
13173
|
}
|
|
@@ -12627,7 +13176,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
12627
13176
|
return out;
|
|
12628
13177
|
}
|
|
12629
13178
|
function gormForeignKeys(file, serviceDir) {
|
|
12630
|
-
if (
|
|
13179
|
+
if (path53.extname(file.path) !== ".go") return [];
|
|
12631
13180
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
12632
13181
|
const tree = parseSource11(makeGoParser4(), file.content);
|
|
12633
13182
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -12642,7 +13191,7 @@ function gormForeignKeys(file, serviceDir) {
|
|
|
12642
13191
|
childTable,
|
|
12643
13192
|
parentTable,
|
|
12644
13193
|
evidence: {
|
|
12645
|
-
file: toPosix(
|
|
13194
|
+
file: toPosix(path53.relative(serviceDir, file.path)),
|
|
12646
13195
|
line,
|
|
12647
13196
|
snippet: snippet(file.content, line)
|
|
12648
13197
|
}
|
|
@@ -12677,6 +13226,121 @@ function gormForeignKeys(file, serviceDir) {
|
|
|
12677
13226
|
return out;
|
|
12678
13227
|
}
|
|
12679
13228
|
|
|
13229
|
+
// src/extract/calls/efcore.ts
|
|
13230
|
+
import path54 from "path";
|
|
13231
|
+
import Parser16 from "tree-sitter";
|
|
13232
|
+
import CSharp2 from "tree-sitter-c-sharp";
|
|
13233
|
+
import { infraId as infraId17 } from "@neat.is/types";
|
|
13234
|
+
var EFCORE_GATE = /Microsoft\.EntityFrameworkCore|DataAnnotations\.Schema|\bDbContext\b|\bDbSet\s*</;
|
|
13235
|
+
var PARSE_CHUNK12 = 16384;
|
|
13236
|
+
function makeCsParser() {
|
|
13237
|
+
const p = new Parser16();
|
|
13238
|
+
p.setLanguage(CSharp2);
|
|
13239
|
+
return p;
|
|
13240
|
+
}
|
|
13241
|
+
function parseSource12(parser, source) {
|
|
13242
|
+
return parser.parse(
|
|
13243
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK12)
|
|
13244
|
+
);
|
|
13245
|
+
}
|
|
13246
|
+
function walk9(node, visit) {
|
|
13247
|
+
visit(node);
|
|
13248
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
13249
|
+
const c = node.namedChild(i);
|
|
13250
|
+
if (c) walk9(c, visit);
|
|
13251
|
+
}
|
|
13252
|
+
}
|
|
13253
|
+
function firstChildOfType(node, type) {
|
|
13254
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
13255
|
+
const c = node.namedChild(i);
|
|
13256
|
+
if (c?.type === type) return c;
|
|
13257
|
+
}
|
|
13258
|
+
return null;
|
|
13259
|
+
}
|
|
13260
|
+
function csStringLiteral(node) {
|
|
13261
|
+
if (node.type === "string_literal") {
|
|
13262
|
+
let out = "";
|
|
13263
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
13264
|
+
const c = node.namedChild(i);
|
|
13265
|
+
if (c?.type === "string_literal_content") out += c.text;
|
|
13266
|
+
}
|
|
13267
|
+
return out;
|
|
13268
|
+
}
|
|
13269
|
+
if (node.type === "verbatim_string_literal") {
|
|
13270
|
+
const t = node.text;
|
|
13271
|
+
return t.length >= 3 ? t.slice(2, -1).replace(/""/g, '"') : "";
|
|
13272
|
+
}
|
|
13273
|
+
return null;
|
|
13274
|
+
}
|
|
13275
|
+
function attributeName(attr) {
|
|
13276
|
+
const nameNode = attr.childForFieldName("name") ?? attr.namedChild(0);
|
|
13277
|
+
if (!nameNode) return null;
|
|
13278
|
+
const text = nameNode.text;
|
|
13279
|
+
const base = text.includes(".") ? text.slice(text.lastIndexOf(".") + 1) : text;
|
|
13280
|
+
return base.endsWith("Attribute") ? base.slice(0, -"Attribute".length) : base;
|
|
13281
|
+
}
|
|
13282
|
+
function tableFromAttribute(attr) {
|
|
13283
|
+
if (attributeName(attr) !== "Table") return null;
|
|
13284
|
+
const args = attr.childForFieldName("arguments") ?? firstChildOfType(attr, "attribute_argument_list");
|
|
13285
|
+
if (!args) return null;
|
|
13286
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
13287
|
+
const arg = args.namedChild(i);
|
|
13288
|
+
if (arg?.type !== "attribute_argument") continue;
|
|
13289
|
+
const first = arg.namedChild(0);
|
|
13290
|
+
if (!first) continue;
|
|
13291
|
+
const value = csStringLiteral(first);
|
|
13292
|
+
if (value !== null) return value;
|
|
13293
|
+
return null;
|
|
13294
|
+
}
|
|
13295
|
+
return null;
|
|
13296
|
+
}
|
|
13297
|
+
function tableFromToTable(call) {
|
|
13298
|
+
const fn = call.childForFieldName("function");
|
|
13299
|
+
if (fn?.type !== "member_access_expression") return null;
|
|
13300
|
+
const method = fn.childForFieldName("name") ?? fn.namedChild(fn.namedChildCount - 1);
|
|
13301
|
+
if (method?.text !== "ToTable") return null;
|
|
13302
|
+
const args = call.childForFieldName("arguments");
|
|
13303
|
+
const firstArg2 = args?.namedChild(0);
|
|
13304
|
+
if (firstArg2?.type !== "argument") return null;
|
|
13305
|
+
const value = firstArg2.namedChild(0);
|
|
13306
|
+
return value ? csStringLiteral(value) : null;
|
|
13307
|
+
}
|
|
13308
|
+
function efcoreEndpointsFromFile(file, serviceDir) {
|
|
13309
|
+
if (path54.extname(file.path) !== ".cs") return [];
|
|
13310
|
+
if (!EFCORE_GATE.test(file.content)) return [];
|
|
13311
|
+
const tree = parseSource12(makeCsParser(), file.content);
|
|
13312
|
+
const out = [];
|
|
13313
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13314
|
+
const push = (name, line) => {
|
|
13315
|
+
if (!name || seen.has(name)) return;
|
|
13316
|
+
seen.add(name);
|
|
13317
|
+
out.push({
|
|
13318
|
+
infraId: infraId17("sql-table", name),
|
|
13319
|
+
name,
|
|
13320
|
+
kind: "sql-table",
|
|
13321
|
+
edgeType: "CALLS",
|
|
13322
|
+
confidenceKind: "structural",
|
|
13323
|
+
evidence: {
|
|
13324
|
+
file: toPosix(path54.relative(serviceDir, file.path)),
|
|
13325
|
+
line,
|
|
13326
|
+
snippet: snippet(file.content, line)
|
|
13327
|
+
}
|
|
13328
|
+
});
|
|
13329
|
+
};
|
|
13330
|
+
walk9(tree.rootNode, (node) => {
|
|
13331
|
+
if (node.type === "attribute") {
|
|
13332
|
+
const table = tableFromAttribute(node);
|
|
13333
|
+
if (table) push(table, node.startPosition.row + 1);
|
|
13334
|
+
return;
|
|
13335
|
+
}
|
|
13336
|
+
if (node.type === "invocation_expression") {
|
|
13337
|
+
const table = tableFromToTable(node);
|
|
13338
|
+
if (table) push(table, node.startPosition.row + 1);
|
|
13339
|
+
}
|
|
13340
|
+
});
|
|
13341
|
+
return out;
|
|
13342
|
+
}
|
|
13343
|
+
|
|
12680
13344
|
// src/extract/calls/index.ts
|
|
12681
13345
|
function edgeTypeFromEndpoint(ep) {
|
|
12682
13346
|
switch (ep.edgeType) {
|
|
@@ -12735,6 +13399,11 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
12735
13399
|
} catch (err) {
|
|
12736
13400
|
recordExtractionError("laravel eloquent extraction", file.path, err);
|
|
12737
13401
|
}
|
|
13402
|
+
try {
|
|
13403
|
+
endpoints.push(...efcoreEndpointsFromFile(file, service.dir));
|
|
13404
|
+
} catch (err) {
|
|
13405
|
+
recordExtractionError("efcore data-axis extraction", file.path, err);
|
|
13406
|
+
}
|
|
12738
13407
|
}
|
|
12739
13408
|
endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
|
|
12740
13409
|
endpoints.push(...pythonOrmCrossFileEndpoints(maskedFiles, service.dir));
|
|
@@ -12838,7 +13507,7 @@ import {
|
|
|
12838
13507
|
Provenance as Provenance16,
|
|
12839
13508
|
confidenceForExtracted as confidenceForExtracted13,
|
|
12840
13509
|
extractedEdgeId as extractedEdgeId10,
|
|
12841
|
-
infraId as
|
|
13510
|
+
infraId as infraId18
|
|
12842
13511
|
} from "@neat.is/types";
|
|
12843
13512
|
async function addTableEdges(graph, services) {
|
|
12844
13513
|
let nodesAdded = 0;
|
|
@@ -12867,8 +13536,8 @@ async function addTableEdges(graph, services) {
|
|
|
12867
13536
|
}
|
|
12868
13537
|
refs.push(...modelRefs);
|
|
12869
13538
|
for (const ref of refs) {
|
|
12870
|
-
const childId =
|
|
12871
|
-
const parentId =
|
|
13539
|
+
const childId = infraId18("sql-table", ref.childTable);
|
|
13540
|
+
const parentId = infraId18("sql-table", ref.parentTable);
|
|
12872
13541
|
if (childId === parentId) continue;
|
|
12873
13542
|
nodesAdded += ensureTableNode(graph, childId, ref.childTable);
|
|
12874
13543
|
nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
|
|
@@ -12903,14 +13572,14 @@ function ensureTableNode(graph, id, name) {
|
|
|
12903
13572
|
}
|
|
12904
13573
|
|
|
12905
13574
|
// src/extract/infra/docker-compose.ts
|
|
12906
|
-
import
|
|
13575
|
+
import path55 from "path";
|
|
12907
13576
|
import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
|
|
12908
13577
|
|
|
12909
13578
|
// src/extract/infra/shared.ts
|
|
12910
|
-
import { NodeType as NodeType26, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as
|
|
13579
|
+
import { NodeType as NodeType26, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId19 } from "@neat.is/types";
|
|
12911
13580
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
12912
13581
|
return {
|
|
12913
|
-
id:
|
|
13582
|
+
id: infraId19(kind, name),
|
|
12914
13583
|
type: NodeType26.InfraNode,
|
|
12915
13584
|
name,
|
|
12916
13585
|
provider,
|
|
@@ -12973,7 +13642,7 @@ function dependsOnList(value) {
|
|
|
12973
13642
|
}
|
|
12974
13643
|
function serviceNameToServiceNode(name, services) {
|
|
12975
13644
|
for (const s of services) {
|
|
12976
|
-
if (s.node.name === name ||
|
|
13645
|
+
if (s.node.name === name || path55.basename(s.dir) === name) return s.node.id;
|
|
12977
13646
|
}
|
|
12978
13647
|
return null;
|
|
12979
13648
|
}
|
|
@@ -12982,7 +13651,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
12982
13651
|
let edgesAdded = 0;
|
|
12983
13652
|
let composePath = null;
|
|
12984
13653
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
12985
|
-
const abs =
|
|
13654
|
+
const abs = path55.join(scanPath, name);
|
|
12986
13655
|
if (await exists(abs)) {
|
|
12987
13656
|
composePath = abs;
|
|
12988
13657
|
break;
|
|
@@ -12995,13 +13664,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
12995
13664
|
} catch (err) {
|
|
12996
13665
|
recordExtractionError(
|
|
12997
13666
|
"infra docker-compose",
|
|
12998
|
-
|
|
13667
|
+
path55.relative(scanPath, composePath),
|
|
12999
13668
|
err
|
|
13000
13669
|
);
|
|
13001
13670
|
return { nodesAdded, edgesAdded };
|
|
13002
13671
|
}
|
|
13003
13672
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
13004
|
-
const evidenceFile =
|
|
13673
|
+
const evidenceFile = path55.relative(scanPath, composePath).split(path55.sep).join("/");
|
|
13005
13674
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
13006
13675
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
13007
13676
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -13042,8 +13711,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
13042
13711
|
}
|
|
13043
13712
|
|
|
13044
13713
|
// src/extract/infra/dockerfile.ts
|
|
13045
|
-
import
|
|
13046
|
-
import { promises as
|
|
13714
|
+
import path56 from "path";
|
|
13715
|
+
import { promises as fs25 } from "fs";
|
|
13047
13716
|
import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
|
|
13048
13717
|
function readDockerfile(content) {
|
|
13049
13718
|
let image = null;
|
|
@@ -13073,15 +13742,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
13073
13742
|
let nodesAdded = 0;
|
|
13074
13743
|
let edgesAdded = 0;
|
|
13075
13744
|
for (const service of services) {
|
|
13076
|
-
const dockerfilePath =
|
|
13745
|
+
const dockerfilePath = path56.join(service.dir, "Dockerfile");
|
|
13077
13746
|
if (!await exists(dockerfilePath)) continue;
|
|
13078
13747
|
let content;
|
|
13079
13748
|
try {
|
|
13080
|
-
content = await
|
|
13749
|
+
content = await fs25.readFile(dockerfilePath, "utf8");
|
|
13081
13750
|
} catch (err) {
|
|
13082
13751
|
recordExtractionError(
|
|
13083
13752
|
"infra dockerfile",
|
|
13084
|
-
|
|
13753
|
+
path56.relative(scanPath, dockerfilePath),
|
|
13085
13754
|
err
|
|
13086
13755
|
);
|
|
13087
13756
|
continue;
|
|
@@ -13093,8 +13762,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
13093
13762
|
graph.addNode(node.id, node);
|
|
13094
13763
|
nodesAdded++;
|
|
13095
13764
|
}
|
|
13096
|
-
const relDockerfile = toPosix(
|
|
13097
|
-
const evidenceFile = toPosix(
|
|
13765
|
+
const relDockerfile = toPosix(path56.relative(service.dir, dockerfilePath));
|
|
13766
|
+
const evidenceFile = toPosix(path56.relative(scanPath, dockerfilePath));
|
|
13098
13767
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
13099
13768
|
graph,
|
|
13100
13769
|
service.pkg.name,
|
|
@@ -13145,23 +13814,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
13145
13814
|
}
|
|
13146
13815
|
|
|
13147
13816
|
// src/extract/infra/terraform.ts
|
|
13148
|
-
import { promises as
|
|
13149
|
-
import
|
|
13817
|
+
import { promises as fs26 } from "fs";
|
|
13818
|
+
import path57 from "path";
|
|
13150
13819
|
import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
|
|
13151
13820
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
13152
13821
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
13153
13822
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
13154
13823
|
if (depth > max) return [];
|
|
13155
13824
|
const out = [];
|
|
13156
|
-
const entries = await
|
|
13825
|
+
const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
13157
13826
|
for (const entry of entries) {
|
|
13158
13827
|
if (entry.isDirectory()) {
|
|
13159
13828
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
13160
|
-
const child =
|
|
13829
|
+
const child = path57.join(start, entry.name);
|
|
13161
13830
|
if (await isPythonVenvDir(child)) continue;
|
|
13162
13831
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
13163
13832
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
13164
|
-
out.push(
|
|
13833
|
+
out.push(path57.join(start, entry.name));
|
|
13165
13834
|
}
|
|
13166
13835
|
}
|
|
13167
13836
|
return out;
|
|
@@ -13192,8 +13861,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
13192
13861
|
let edgesAdded = 0;
|
|
13193
13862
|
const files = await walkTfFiles(scanPath);
|
|
13194
13863
|
for (const file of files) {
|
|
13195
|
-
const content = await
|
|
13196
|
-
const evidenceFile = toPosix(
|
|
13864
|
+
const content = await fs26.readFile(file, "utf8");
|
|
13865
|
+
const evidenceFile = toPosix(path57.relative(scanPath, file));
|
|
13197
13866
|
const resources = [];
|
|
13198
13867
|
const byKey = /* @__PURE__ */ new Map();
|
|
13199
13868
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -13249,8 +13918,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
13249
13918
|
}
|
|
13250
13919
|
|
|
13251
13920
|
// src/extract/infra/k8s.ts
|
|
13252
|
-
import { promises as
|
|
13253
|
-
import
|
|
13921
|
+
import { promises as fs27 } from "fs";
|
|
13922
|
+
import path58 from "path";
|
|
13254
13923
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
13255
13924
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
13256
13925
|
Service: "k8s-service",
|
|
@@ -13264,15 +13933,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
13264
13933
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
13265
13934
|
if (depth > max) return [];
|
|
13266
13935
|
const out = [];
|
|
13267
|
-
const entries = await
|
|
13936
|
+
const entries = await fs27.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
13268
13937
|
for (const entry of entries) {
|
|
13269
13938
|
if (entry.isDirectory()) {
|
|
13270
13939
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
13271
|
-
const child =
|
|
13940
|
+
const child = path58.join(start, entry.name);
|
|
13272
13941
|
if (await isPythonVenvDir(child)) continue;
|
|
13273
13942
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
13274
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
13275
|
-
out.push(
|
|
13943
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path58.extname(entry.name))) {
|
|
13944
|
+
out.push(path58.join(start, entry.name));
|
|
13276
13945
|
}
|
|
13277
13946
|
}
|
|
13278
13947
|
return out;
|
|
@@ -13281,7 +13950,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
13281
13950
|
let nodesAdded = 0;
|
|
13282
13951
|
const files = await walkYamlFiles2(scanPath);
|
|
13283
13952
|
for (const file of files) {
|
|
13284
|
-
const content = await
|
|
13953
|
+
const content = await fs27.readFile(file, "utf8");
|
|
13285
13954
|
let docs;
|
|
13286
13955
|
try {
|
|
13287
13956
|
docs = parseAllDocuments2(content).map((d) => d.toJSON());
|
|
@@ -13304,16 +13973,16 @@ async function addK8sResources(graph, scanPath) {
|
|
|
13304
13973
|
}
|
|
13305
13974
|
|
|
13306
13975
|
// src/extract/infra/cloudflare.ts
|
|
13307
|
-
import { promises as
|
|
13308
|
-
import
|
|
13976
|
+
import { promises as fs28 } from "fs";
|
|
13977
|
+
import path59 from "path";
|
|
13309
13978
|
import { parse as parseToml3 } from "smol-toml";
|
|
13310
13979
|
import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
|
|
13311
13980
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
13312
13981
|
async function readWranglerConfig(dir) {
|
|
13313
13982
|
for (const filename of WRANGLER_FILENAMES) {
|
|
13314
|
-
const abs =
|
|
13983
|
+
const abs = path59.join(dir, filename);
|
|
13315
13984
|
if (!await exists(abs)) continue;
|
|
13316
|
-
const raw = await
|
|
13985
|
+
const raw = await fs28.readFile(abs, "utf8");
|
|
13317
13986
|
const config = filename === "wrangler.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
13318
13987
|
return { config, relFile: filename, raw };
|
|
13319
13988
|
}
|
|
@@ -13374,11 +14043,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
13374
14043
|
try {
|
|
13375
14044
|
read = await readWranglerConfig(service.dir);
|
|
13376
14045
|
} catch (err) {
|
|
13377
|
-
recordExtractionError("infra cloudflare",
|
|
14046
|
+
recordExtractionError("infra cloudflare", path59.relative(scanPath, service.dir), err);
|
|
13378
14047
|
continue;
|
|
13379
14048
|
}
|
|
13380
14049
|
if (!read || !read.config.name) continue;
|
|
13381
|
-
const evidenceFile = toPosix(
|
|
14050
|
+
const evidenceFile = toPosix(path59.relative(scanPath, path59.join(service.dir, read.relFile)));
|
|
13382
14051
|
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
13383
14052
|
}
|
|
13384
14053
|
for (const worker of discovered) {
|
|
@@ -13390,7 +14059,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
13390
14059
|
}
|
|
13391
14060
|
let anchorId = service.node.id;
|
|
13392
14061
|
if (config.main) {
|
|
13393
|
-
const entryRelPath = toPosix(
|
|
14062
|
+
const entryRelPath = toPosix(path59.normalize(config.main));
|
|
13394
14063
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
13395
14064
|
graph,
|
|
13396
14065
|
service.pkg.name,
|
|
@@ -13536,24 +14205,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
13536
14205
|
}
|
|
13537
14206
|
|
|
13538
14207
|
// src/extract/infra/vercel.ts
|
|
13539
|
-
import { promises as
|
|
13540
|
-
import
|
|
14208
|
+
import { promises as fs29 } from "fs";
|
|
14209
|
+
import path60 from "path";
|
|
13541
14210
|
import { EdgeType as EdgeType21 } from "@neat.is/types";
|
|
13542
14211
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
13543
14212
|
async function readVercelConfig(dir) {
|
|
13544
14213
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
13545
|
-
const abs =
|
|
14214
|
+
const abs = path60.join(dir, filename);
|
|
13546
14215
|
if (!await exists(abs)) continue;
|
|
13547
|
-
const raw = await
|
|
14216
|
+
const raw = await fs29.readFile(abs, "utf8");
|
|
13548
14217
|
const config = JSON.parse(maskCommentsInSource(raw));
|
|
13549
14218
|
return { config, relFile: filename, raw };
|
|
13550
14219
|
}
|
|
13551
14220
|
return null;
|
|
13552
14221
|
}
|
|
13553
14222
|
async function readLinkedProjectName(dir) {
|
|
13554
|
-
const abs =
|
|
14223
|
+
const abs = path60.join(dir, ".vercel", "project.json");
|
|
13555
14224
|
if (!await exists(abs)) return void 0;
|
|
13556
|
-
const parsed = JSON.parse(await
|
|
14225
|
+
const parsed = JSON.parse(await fs29.readFile(abs, "utf8"));
|
|
13557
14226
|
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
13558
14227
|
}
|
|
13559
14228
|
function routeSource(route) {
|
|
@@ -13569,7 +14238,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
13569
14238
|
read = await readVercelConfig(service.dir);
|
|
13570
14239
|
projectName = await readLinkedProjectName(service.dir);
|
|
13571
14240
|
} catch (err) {
|
|
13572
|
-
recordExtractionError("infra vercel",
|
|
14241
|
+
recordExtractionError("infra vercel", path60.relative(scanPath, service.dir), err);
|
|
13573
14242
|
continue;
|
|
13574
14243
|
}
|
|
13575
14244
|
if (!read && !projectName) continue;
|
|
@@ -13585,7 +14254,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
13585
14254
|
const anchorId = service.node.id;
|
|
13586
14255
|
if (!read) continue;
|
|
13587
14256
|
const { config, relFile, raw } = read;
|
|
13588
|
-
const evidenceFile = toPosix(
|
|
14257
|
+
const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
|
|
13589
14258
|
const add = (edgeType, kind, name) => {
|
|
13590
14259
|
if (!name) return;
|
|
13591
14260
|
const result = emitPlatformResourceEdge(
|
|
@@ -13613,16 +14282,16 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
13613
14282
|
}
|
|
13614
14283
|
|
|
13615
14284
|
// src/extract/infra/railway.ts
|
|
13616
|
-
import { promises as
|
|
13617
|
-
import
|
|
14285
|
+
import { promises as fs30 } from "fs";
|
|
14286
|
+
import path61 from "path";
|
|
13618
14287
|
import { parse as parseToml4 } from "smol-toml";
|
|
13619
14288
|
import { EdgeType as EdgeType22 } from "@neat.is/types";
|
|
13620
14289
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
13621
14290
|
async function readRailwayConfig(dir) {
|
|
13622
14291
|
for (const filename of RAILWAY_FILENAMES) {
|
|
13623
|
-
const abs =
|
|
14292
|
+
const abs = path61.join(dir, filename);
|
|
13624
14293
|
if (!await exists(abs)) continue;
|
|
13625
|
-
const raw = await
|
|
14294
|
+
const raw = await fs30.readFile(abs, "utf8");
|
|
13626
14295
|
const config = filename === "railway.toml" ? parseToml4(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
13627
14296
|
return { config, relFile: filename, raw };
|
|
13628
14297
|
}
|
|
@@ -13636,7 +14305,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
13636
14305
|
try {
|
|
13637
14306
|
read = await readRailwayConfig(service.dir);
|
|
13638
14307
|
} catch (err) {
|
|
13639
|
-
recordExtractionError("infra railway",
|
|
14308
|
+
recordExtractionError("infra railway", path61.relative(scanPath, service.dir), err);
|
|
13640
14309
|
continue;
|
|
13641
14310
|
}
|
|
13642
14311
|
if (!read) continue;
|
|
@@ -13646,7 +14315,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
13646
14315
|
}
|
|
13647
14316
|
const anchorId = service.node.id;
|
|
13648
14317
|
const { config, relFile, raw } = read;
|
|
13649
|
-
const evidenceFile = toPosix(
|
|
14318
|
+
const evidenceFile = toPosix(path61.relative(scanPath, path61.join(service.dir, relFile)));
|
|
13650
14319
|
const add = (edgeType, kind, name) => {
|
|
13651
14320
|
if (!name) return;
|
|
13652
14321
|
const result = emitPlatformResourceEdge(
|
|
@@ -13670,15 +14339,15 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
13670
14339
|
}
|
|
13671
14340
|
|
|
13672
14341
|
// src/extract/infra/supabase.ts
|
|
13673
|
-
import { promises as
|
|
13674
|
-
import
|
|
14342
|
+
import { promises as fs31 } from "fs";
|
|
14343
|
+
import path62 from "path";
|
|
13675
14344
|
import { parse as parseToml5 } from "smol-toml";
|
|
13676
14345
|
import { EdgeType as EdgeType23 } from "@neat.is/types";
|
|
13677
14346
|
async function readSupabaseConfig(dir) {
|
|
13678
|
-
const relFile =
|
|
13679
|
-
const abs =
|
|
14347
|
+
const relFile = path62.join("supabase", "config.toml");
|
|
14348
|
+
const abs = path62.join(dir, relFile);
|
|
13680
14349
|
if (!await exists(abs)) return null;
|
|
13681
|
-
const raw = await
|
|
14350
|
+
const raw = await fs31.readFile(abs, "utf8");
|
|
13682
14351
|
const config = parseToml5(raw);
|
|
13683
14352
|
return { config, relFile, raw };
|
|
13684
14353
|
}
|
|
@@ -13690,7 +14359,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
13690
14359
|
try {
|
|
13691
14360
|
read = await readSupabaseConfig(service.dir);
|
|
13692
14361
|
} catch (err) {
|
|
13693
|
-
recordExtractionError("infra supabase",
|
|
14362
|
+
recordExtractionError("infra supabase", path62.relative(scanPath, service.dir), err);
|
|
13694
14363
|
continue;
|
|
13695
14364
|
}
|
|
13696
14365
|
if (!read) continue;
|
|
@@ -13705,7 +14374,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
13705
14374
|
});
|
|
13706
14375
|
}
|
|
13707
14376
|
const anchorId = service.node.id;
|
|
13708
|
-
const evidenceFile = toPosix(
|
|
14377
|
+
const evidenceFile = toPosix(path62.relative(scanPath, path62.join(service.dir, relFile)));
|
|
13709
14378
|
const add = (edgeType, kind, name) => {
|
|
13710
14379
|
if (!name) return;
|
|
13711
14380
|
const result = emitPlatformResourceEdge(
|
|
@@ -13746,8 +14415,8 @@ async function addInfra(graph, scanPath, services) {
|
|
|
13746
14415
|
}
|
|
13747
14416
|
|
|
13748
14417
|
// src/extract/zod-shapes.ts
|
|
13749
|
-
import
|
|
13750
|
-
import
|
|
14418
|
+
import path63 from "path";
|
|
14419
|
+
import Parser17 from "tree-sitter";
|
|
13751
14420
|
import JavaScript8 from "tree-sitter-javascript";
|
|
13752
14421
|
import {
|
|
13753
14422
|
EdgeType as EdgeType24,
|
|
@@ -13755,12 +14424,12 @@ import {
|
|
|
13755
14424
|
Provenance as Provenance22,
|
|
13756
14425
|
confidenceForExtracted as confidenceForExtracted19,
|
|
13757
14426
|
extractedEdgeId as extractedEdgeId11,
|
|
13758
|
-
infraId as
|
|
14427
|
+
infraId as infraId20
|
|
13759
14428
|
} from "@neat.is/types";
|
|
13760
14429
|
var ZOD_IMPORT_RE = /\bzod\b/;
|
|
13761
14430
|
var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
|
|
13762
14431
|
function parserForExt4(ext) {
|
|
13763
|
-
const p = new
|
|
14432
|
+
const p = new Parser17();
|
|
13764
14433
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript8);
|
|
13765
14434
|
return p;
|
|
13766
14435
|
}
|
|
@@ -13848,7 +14517,7 @@ function topLevelSchemas(root) {
|
|
|
13848
14517
|
}
|
|
13849
14518
|
function zodShapesFromFile(file, serviceDir) {
|
|
13850
14519
|
if (!ZOD_IMPORT_RE.test(file.content)) return [];
|
|
13851
|
-
const tree = parseSource3(parserForExt4(
|
|
14520
|
+
const tree = parseSource3(parserForExt4(path63.extname(file.path)), file.content);
|
|
13852
14521
|
const out = [];
|
|
13853
14522
|
const seen = /* @__PURE__ */ new Set();
|
|
13854
14523
|
for (const { name, call } of topLevelSchemas(tree.rootNode)) {
|
|
@@ -13862,11 +14531,11 @@ function zodShapesFromFile(file, serviceDir) {
|
|
|
13862
14531
|
seen.add(name);
|
|
13863
14532
|
const line = call.startPosition.row + 1;
|
|
13864
14533
|
out.push({
|
|
13865
|
-
infraId:
|
|
14534
|
+
infraId: infraId20("zod-schema", name),
|
|
13866
14535
|
name,
|
|
13867
14536
|
fields,
|
|
13868
14537
|
evidence: {
|
|
13869
|
-
file:
|
|
14538
|
+
file: path63.relative(serviceDir, file.path),
|
|
13870
14539
|
line,
|
|
13871
14540
|
snippet: snippet(file.content, line)
|
|
13872
14541
|
}
|
|
@@ -14102,11 +14771,11 @@ async function addFirestoreRules(graph, services) {
|
|
|
14102
14771
|
}
|
|
14103
14772
|
|
|
14104
14773
|
// src/extract/index.ts
|
|
14105
|
-
import
|
|
14774
|
+
import path65 from "path";
|
|
14106
14775
|
|
|
14107
14776
|
// src/extract/retire.ts
|
|
14108
14777
|
import { existsSync as existsSync2 } from "fs";
|
|
14109
|
-
import
|
|
14778
|
+
import path64 from "path";
|
|
14110
14779
|
import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
|
|
14111
14780
|
function dropOrphanedFileNodes(graph) {
|
|
14112
14781
|
const orphans = [];
|
|
@@ -14140,11 +14809,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
14140
14809
|
if (edge.provenance !== Provenance23.EXTRACTED) return;
|
|
14141
14810
|
const evidenceFile = edge.evidence?.file;
|
|
14142
14811
|
if (!evidenceFile) return;
|
|
14143
|
-
if (
|
|
14812
|
+
if (path64.isAbsolute(evidenceFile)) {
|
|
14144
14813
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
14145
14814
|
return;
|
|
14146
14815
|
}
|
|
14147
|
-
const found = bases.some((base) => existsSync2(
|
|
14816
|
+
const found = bases.some((base) => existsSync2(path64.join(base, evidenceFile)));
|
|
14148
14817
|
if (!found) toDrop.push(id);
|
|
14149
14818
|
});
|
|
14150
14819
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -14201,7 +14870,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
14201
14870
|
}
|
|
14202
14871
|
const droppedEntries = drainDroppedExtracted();
|
|
14203
14872
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
14204
|
-
const rejectedPath =
|
|
14873
|
+
const rejectedPath = path65.join(path65.dirname(opts.errorsPath), "rejected.ndjson");
|
|
14205
14874
|
try {
|
|
14206
14875
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
14207
14876
|
} catch (err) {
|
|
@@ -14575,8 +15244,8 @@ function computeDivergences(graph, opts = {}) {
|
|
|
14575
15244
|
}
|
|
14576
15245
|
|
|
14577
15246
|
// src/persist.ts
|
|
14578
|
-
import { promises as
|
|
14579
|
-
import
|
|
15247
|
+
import { promises as fs32 } from "fs";
|
|
15248
|
+
import path66 from "path";
|
|
14580
15249
|
import { NodeType as NodeType31, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
14581
15250
|
var SCHEMA_VERSION = 7;
|
|
14582
15251
|
function migrateV1ToV2(payload) {
|
|
@@ -14631,7 +15300,7 @@ function migrateV2ToV3(payload) {
|
|
|
14631
15300
|
return { ...payload, schemaVersion: 3 };
|
|
14632
15301
|
}
|
|
14633
15302
|
async function ensureDir(filePath) {
|
|
14634
|
-
await
|
|
15303
|
+
await fs32.mkdir(path66.dirname(filePath), { recursive: true });
|
|
14635
15304
|
}
|
|
14636
15305
|
async function saveGraphToDisk(graph, outPath) {
|
|
14637
15306
|
await ensureDir(outPath);
|
|
@@ -14641,13 +15310,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
14641
15310
|
graph: graph.export()
|
|
14642
15311
|
};
|
|
14643
15312
|
const tmp = `${outPath}.tmp`;
|
|
14644
|
-
await
|
|
14645
|
-
await
|
|
15313
|
+
await fs32.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
15314
|
+
await fs32.rename(tmp, outPath);
|
|
14646
15315
|
}
|
|
14647
15316
|
async function loadGraphFromDisk(graph, outPath) {
|
|
14648
15317
|
let raw;
|
|
14649
15318
|
try {
|
|
14650
|
-
raw = await
|
|
15319
|
+
raw = await fs32.readFile(outPath, "utf8");
|
|
14651
15320
|
} catch (err) {
|
|
14652
15321
|
if (err.code === "ENOENT") return;
|
|
14653
15322
|
throw err;
|
|
@@ -14720,7 +15389,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
14720
15389
|
}
|
|
14721
15390
|
|
|
14722
15391
|
// src/diff.ts
|
|
14723
|
-
import { promises as
|
|
15392
|
+
import { promises as fs33 } from "fs";
|
|
14724
15393
|
async function loadSnapshotForDiff(target) {
|
|
14725
15394
|
if (/^https?:\/\//i.test(target)) {
|
|
14726
15395
|
const res = await fetch(target);
|
|
@@ -14729,7 +15398,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
14729
15398
|
}
|
|
14730
15399
|
return await res.json();
|
|
14731
15400
|
}
|
|
14732
|
-
const raw = await
|
|
15401
|
+
const raw = await fs33.readFile(target, "utf8");
|
|
14733
15402
|
return JSON.parse(raw);
|
|
14734
15403
|
}
|
|
14735
15404
|
function indexEntries(entries) {
|
|
@@ -14796,23 +15465,23 @@ function canonicalJson(value) {
|
|
|
14796
15465
|
}
|
|
14797
15466
|
|
|
14798
15467
|
// src/projects.ts
|
|
14799
|
-
import
|
|
15468
|
+
import path67 from "path";
|
|
14800
15469
|
function pathsForProject(project, baseDir) {
|
|
14801
15470
|
if (project === DEFAULT_PROJECT) {
|
|
14802
15471
|
return {
|
|
14803
|
-
snapshotPath:
|
|
14804
|
-
errorsPath:
|
|
14805
|
-
staleEventsPath:
|
|
14806
|
-
embeddingsCachePath:
|
|
14807
|
-
policyViolationsPath:
|
|
15472
|
+
snapshotPath: path67.join(baseDir, "graph.json"),
|
|
15473
|
+
errorsPath: path67.join(baseDir, "errors.ndjson"),
|
|
15474
|
+
staleEventsPath: path67.join(baseDir, "stale-events.ndjson"),
|
|
15475
|
+
embeddingsCachePath: path67.join(baseDir, "embeddings.json"),
|
|
15476
|
+
policyViolationsPath: path67.join(baseDir, "policy-violations.ndjson")
|
|
14808
15477
|
};
|
|
14809
15478
|
}
|
|
14810
15479
|
return {
|
|
14811
|
-
snapshotPath:
|
|
14812
|
-
errorsPath:
|
|
14813
|
-
staleEventsPath:
|
|
14814
|
-
embeddingsCachePath:
|
|
14815
|
-
policyViolationsPath:
|
|
15480
|
+
snapshotPath: path67.join(baseDir, `${project}.json`),
|
|
15481
|
+
errorsPath: path67.join(baseDir, `errors.${project}.ndjson`),
|
|
15482
|
+
staleEventsPath: path67.join(baseDir, `stale-events.${project}.ndjson`),
|
|
15483
|
+
embeddingsCachePath: path67.join(baseDir, `embeddings.${project}.json`),
|
|
15484
|
+
policyViolationsPath: path67.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
14816
15485
|
};
|
|
14817
15486
|
}
|
|
14818
15487
|
var Projects = class {
|
|
@@ -14851,9 +15520,9 @@ function parseExtraProjects(raw) {
|
|
|
14851
15520
|
}
|
|
14852
15521
|
|
|
14853
15522
|
// src/registry.ts
|
|
14854
|
-
import { promises as
|
|
15523
|
+
import { promises as fs34 } from "fs";
|
|
14855
15524
|
import os2 from "os";
|
|
14856
|
-
import
|
|
15525
|
+
import path68 from "path";
|
|
14857
15526
|
import {
|
|
14858
15527
|
RegistryFileSchema
|
|
14859
15528
|
} from "@neat.is/types";
|
|
@@ -14861,20 +15530,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
14861
15530
|
var LOCK_RETRY_MS = 50;
|
|
14862
15531
|
function neatHome() {
|
|
14863
15532
|
const override = process.env.NEAT_HOME;
|
|
14864
|
-
if (override && override.length > 0) return
|
|
14865
|
-
return
|
|
15533
|
+
if (override && override.length > 0) return path68.resolve(override);
|
|
15534
|
+
return path68.join(os2.homedir(), ".neat");
|
|
14866
15535
|
}
|
|
14867
15536
|
function registryPath() {
|
|
14868
|
-
return
|
|
15537
|
+
return path68.join(neatHome(), "projects.json");
|
|
14869
15538
|
}
|
|
14870
15539
|
function registryLockPath() {
|
|
14871
|
-
return
|
|
15540
|
+
return path68.join(neatHome(), "projects.json.lock");
|
|
14872
15541
|
}
|
|
14873
15542
|
function daemonPidPath() {
|
|
14874
|
-
return
|
|
15543
|
+
return path68.join(neatHome(), "neatd.pid");
|
|
14875
15544
|
}
|
|
14876
15545
|
function daemonsDir() {
|
|
14877
|
-
return
|
|
15546
|
+
return path68.join(neatHome(), "daemons");
|
|
14878
15547
|
}
|
|
14879
15548
|
function isFiniteInt(v) {
|
|
14880
15549
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -14907,7 +15576,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
14907
15576
|
const dir = daemonsDir();
|
|
14908
15577
|
let names;
|
|
14909
15578
|
try {
|
|
14910
|
-
names = await
|
|
15579
|
+
names = await fs34.readdir(dir);
|
|
14911
15580
|
} catch (err) {
|
|
14912
15581
|
if (err.code === "ENOENT") return [];
|
|
14913
15582
|
throw err;
|
|
@@ -14915,10 +15584,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
14915
15584
|
const out = [];
|
|
14916
15585
|
for (const name of names) {
|
|
14917
15586
|
if (!name.endsWith(".json")) continue;
|
|
14918
|
-
const file =
|
|
15587
|
+
const file = path68.join(dir, name);
|
|
14919
15588
|
let raw;
|
|
14920
15589
|
try {
|
|
14921
|
-
raw = await
|
|
15590
|
+
raw = await fs34.readFile(file, "utf8");
|
|
14922
15591
|
} catch {
|
|
14923
15592
|
continue;
|
|
14924
15593
|
}
|
|
@@ -14931,7 +15600,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
14931
15600
|
return out;
|
|
14932
15601
|
}
|
|
14933
15602
|
async function removeDaemonRecord(source) {
|
|
14934
|
-
await
|
|
15603
|
+
await fs34.unlink(source).catch(() => {
|
|
14935
15604
|
});
|
|
14936
15605
|
}
|
|
14937
15606
|
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
@@ -14988,7 +15657,7 @@ function isPidAliveDefault(pid) {
|
|
|
14988
15657
|
}
|
|
14989
15658
|
async function readPidFile(file) {
|
|
14990
15659
|
try {
|
|
14991
|
-
const raw = await
|
|
15660
|
+
const raw = await fs34.readFile(file, "utf8");
|
|
14992
15661
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
14993
15662
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
14994
15663
|
} catch {
|
|
@@ -15036,32 +15705,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
15036
15705
|
}
|
|
15037
15706
|
}
|
|
15038
15707
|
async function normalizeProjectPath(input) {
|
|
15039
|
-
const resolved =
|
|
15708
|
+
const resolved = path68.resolve(input);
|
|
15040
15709
|
try {
|
|
15041
|
-
return await
|
|
15710
|
+
return await fs34.realpath(resolved);
|
|
15042
15711
|
} catch {
|
|
15043
15712
|
return resolved;
|
|
15044
15713
|
}
|
|
15045
15714
|
}
|
|
15046
15715
|
async function writeAtomically(target, contents) {
|
|
15047
|
-
await
|
|
15716
|
+
await fs34.mkdir(path68.dirname(target), { recursive: true });
|
|
15048
15717
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
15049
|
-
const fd = await
|
|
15718
|
+
const fd = await fs34.open(tmp, "w");
|
|
15050
15719
|
try {
|
|
15051
15720
|
await fd.writeFile(contents, "utf8");
|
|
15052
15721
|
await fd.sync();
|
|
15053
15722
|
} finally {
|
|
15054
15723
|
await fd.close();
|
|
15055
15724
|
}
|
|
15056
|
-
await
|
|
15725
|
+
await fs34.rename(tmp, target);
|
|
15057
15726
|
}
|
|
15058
15727
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
15059
15728
|
const deadline = Date.now() + timeoutMs;
|
|
15060
|
-
await
|
|
15729
|
+
await fs34.mkdir(path68.dirname(lockPath), { recursive: true });
|
|
15061
15730
|
let probedHolder = false;
|
|
15062
15731
|
while (true) {
|
|
15063
15732
|
try {
|
|
15064
|
-
const fd = await
|
|
15733
|
+
const fd = await fs34.open(lockPath, "wx");
|
|
15065
15734
|
try {
|
|
15066
15735
|
await fd.writeFile(`${process.pid}
|
|
15067
15736
|
`, "utf8");
|
|
@@ -15086,7 +15755,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
15086
15755
|
}
|
|
15087
15756
|
}
|
|
15088
15757
|
async function releaseLock(lockPath) {
|
|
15089
|
-
await
|
|
15758
|
+
await fs34.unlink(lockPath).catch(() => {
|
|
15090
15759
|
});
|
|
15091
15760
|
}
|
|
15092
15761
|
async function withLock(fn) {
|
|
@@ -15102,7 +15771,7 @@ async function readRegistry() {
|
|
|
15102
15771
|
const file = registryPath();
|
|
15103
15772
|
let raw;
|
|
15104
15773
|
try {
|
|
15105
|
-
raw = await
|
|
15774
|
+
raw = await fs34.readFile(file, "utf8");
|
|
15106
15775
|
} catch (err) {
|
|
15107
15776
|
if (err.code === "ENOENT") {
|
|
15108
15777
|
return { version: 1, projects: [] };
|
|
@@ -15207,7 +15876,7 @@ function pruneTtlMs() {
|
|
|
15207
15876
|
}
|
|
15208
15877
|
async function statPathStatus(p) {
|
|
15209
15878
|
try {
|
|
15210
|
-
const stat = await
|
|
15879
|
+
const stat = await fs34.stat(p);
|
|
15211
15880
|
return stat.isDirectory() ? "present" : "unknown";
|
|
15212
15881
|
} catch (err) {
|
|
15213
15882
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -15252,14 +15921,14 @@ import cors from "@fastify/cors";
|
|
|
15252
15921
|
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
15253
15922
|
|
|
15254
15923
|
// src/extend/index.ts
|
|
15255
|
-
import { promises as
|
|
15256
|
-
import
|
|
15924
|
+
import { promises as fs36 } from "fs";
|
|
15925
|
+
import path70 from "path";
|
|
15257
15926
|
import os3 from "os";
|
|
15258
15927
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
15259
15928
|
|
|
15260
15929
|
// src/installers/package-manager.ts
|
|
15261
|
-
import { promises as
|
|
15262
|
-
import
|
|
15930
|
+
import { promises as fs35 } from "fs";
|
|
15931
|
+
import path69 from "path";
|
|
15263
15932
|
import { spawn } from "child_process";
|
|
15264
15933
|
var LOCKFILE_PRIORITY = [
|
|
15265
15934
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -15274,29 +15943,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
15274
15943
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
15275
15944
|
async function exists2(p) {
|
|
15276
15945
|
try {
|
|
15277
|
-
await
|
|
15946
|
+
await fs35.access(p);
|
|
15278
15947
|
return true;
|
|
15279
15948
|
} catch {
|
|
15280
15949
|
return false;
|
|
15281
15950
|
}
|
|
15282
15951
|
}
|
|
15283
15952
|
async function detectPackageManager(serviceDir) {
|
|
15284
|
-
let dir =
|
|
15953
|
+
let dir = path69.resolve(serviceDir);
|
|
15285
15954
|
const stops = /* @__PURE__ */ new Set();
|
|
15286
15955
|
for (let i = 0; i < 64; i++) {
|
|
15287
15956
|
if (stops.has(dir)) break;
|
|
15288
15957
|
stops.add(dir);
|
|
15289
15958
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
15290
|
-
const lockPath =
|
|
15959
|
+
const lockPath = path69.join(dir, candidate.lockfile);
|
|
15291
15960
|
if (await exists2(lockPath)) {
|
|
15292
15961
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
15293
15962
|
}
|
|
15294
15963
|
}
|
|
15295
|
-
const parent =
|
|
15964
|
+
const parent = path69.dirname(dir);
|
|
15296
15965
|
if (parent === dir) break;
|
|
15297
15966
|
dir = parent;
|
|
15298
15967
|
}
|
|
15299
|
-
return { pm: "npm", cwd:
|
|
15968
|
+
return { pm: "npm", cwd: path69.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
15300
15969
|
}
|
|
15301
15970
|
async function runPackageManagerInstall(cmd) {
|
|
15302
15971
|
return new Promise((resolve) => {
|
|
@@ -15338,15 +16007,15 @@ ${err.message}`
|
|
|
15338
16007
|
// src/extend/index.ts
|
|
15339
16008
|
async function fileExists2(p) {
|
|
15340
16009
|
try {
|
|
15341
|
-
await
|
|
16010
|
+
await fs36.access(p);
|
|
15342
16011
|
return true;
|
|
15343
16012
|
} catch {
|
|
15344
16013
|
return false;
|
|
15345
16014
|
}
|
|
15346
16015
|
}
|
|
15347
16016
|
async function readPackageJson(scanPath) {
|
|
15348
|
-
const pkgPath =
|
|
15349
|
-
const raw = await
|
|
16017
|
+
const pkgPath = path70.join(scanPath, "package.json");
|
|
16018
|
+
const raw = await fs36.readFile(pkgPath, "utf8");
|
|
15350
16019
|
return JSON.parse(raw);
|
|
15351
16020
|
}
|
|
15352
16021
|
var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -15359,27 +16028,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
15359
16028
|
]);
|
|
15360
16029
|
async function findHookFiles(scanPath) {
|
|
15361
16030
|
const found = [];
|
|
15362
|
-
const
|
|
15363
|
-
const entries = await
|
|
16031
|
+
const walk10 = async (dir) => {
|
|
16032
|
+
const entries = await fs36.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
15364
16033
|
for (const entry of entries) {
|
|
15365
16034
|
if (entry.isDirectory()) {
|
|
15366
16035
|
if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
|
|
15367
|
-
await
|
|
16036
|
+
await walk10(path70.join(dir, entry.name));
|
|
15368
16037
|
} else if (entry.isFile()) {
|
|
15369
16038
|
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(
|
|
16039
|
+
const rel = path70.relative(scanPath, path70.join(dir, entry.name));
|
|
16040
|
+
found.push(rel.split(path70.sep).join("/"));
|
|
15372
16041
|
}
|
|
15373
16042
|
}
|
|
15374
16043
|
}
|
|
15375
16044
|
};
|
|
15376
|
-
await
|
|
16045
|
+
await walk10(scanPath);
|
|
15377
16046
|
return found.sort();
|
|
15378
16047
|
}
|
|
15379
16048
|
async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
15380
16049
|
let fallback = null;
|
|
15381
16050
|
for (const file of hookFiles) {
|
|
15382
|
-
const content = await
|
|
16051
|
+
const content = await fs36.readFile(path70.join(scanPath, file), "utf8");
|
|
15383
16052
|
const patched = splicedContent(content, snippet2);
|
|
15384
16053
|
if (patched !== null) return { file, content, patched };
|
|
15385
16054
|
if (fallback === null) fallback = { file, content };
|
|
@@ -15387,12 +16056,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
|
15387
16056
|
return { file: fallback.file, content: fallback.content, patched: null };
|
|
15388
16057
|
}
|
|
15389
16058
|
function extendLogPath() {
|
|
15390
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
16059
|
+
return process.env.NEAT_EXTEND_LOG ?? path70.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
15391
16060
|
}
|
|
15392
16061
|
async function appendExtendLog(entry) {
|
|
15393
16062
|
const logPath = extendLogPath();
|
|
15394
|
-
await
|
|
15395
|
-
await
|
|
16063
|
+
await fs36.mkdir(path70.dirname(logPath), { recursive: true });
|
|
16064
|
+
await fs36.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
15396
16065
|
}
|
|
15397
16066
|
function splicedContent(fileContent, snippet2) {
|
|
15398
16067
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -15450,7 +16119,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
15450
16119
|
}
|
|
15451
16120
|
async function describeProjectInstrumentation(ctx) {
|
|
15452
16121
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
15453
|
-
const envNeat = await fileExists2(
|
|
16122
|
+
const envNeat = await fileExists2(path70.join(ctx.scanPath, ".env.neat"));
|
|
15454
16123
|
const registryInstrPackages = new Set(
|
|
15455
16124
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
15456
16125
|
);
|
|
@@ -15472,7 +16141,7 @@ async function applyExtension(ctx, args, options) {
|
|
|
15472
16141
|
);
|
|
15473
16142
|
}
|
|
15474
16143
|
for (const file of hookFiles) {
|
|
15475
|
-
const content = await
|
|
16144
|
+
const content = await fs36.readFile(path70.join(ctx.scanPath, file), "utf8");
|
|
15476
16145
|
if (content.includes(args.registration_snippet)) {
|
|
15477
16146
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
15478
16147
|
}
|
|
@@ -15484,18 +16153,18 @@ async function applyExtension(ctx, args, options) {
|
|
|
15484
16153
|
);
|
|
15485
16154
|
}
|
|
15486
16155
|
const primaryFile = primary.file;
|
|
15487
|
-
const primaryPath =
|
|
16156
|
+
const primaryPath = path70.join(ctx.scanPath, primaryFile);
|
|
15488
16157
|
const filesTouched = [];
|
|
15489
16158
|
const depsAdded = [];
|
|
15490
|
-
const pkgPath =
|
|
16159
|
+
const pkgPath = path70.join(ctx.scanPath, "package.json");
|
|
15491
16160
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
15492
16161
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
15493
16162
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
15494
|
-
await
|
|
16163
|
+
await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
15495
16164
|
filesTouched.push("package.json");
|
|
15496
16165
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
15497
16166
|
}
|
|
15498
|
-
await
|
|
16167
|
+
await fs36.writeFile(primaryPath, primary.patched, "utf8");
|
|
15499
16168
|
filesTouched.push(primaryFile);
|
|
15500
16169
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
15501
16170
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -15526,7 +16195,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
15526
16195
|
};
|
|
15527
16196
|
}
|
|
15528
16197
|
for (const file of hookFiles) {
|
|
15529
|
-
const content = await
|
|
16198
|
+
const content = await fs36.readFile(path70.join(ctx.scanPath, file), "utf8");
|
|
15530
16199
|
if (content.includes(args.registration_snippet)) {
|
|
15531
16200
|
return {
|
|
15532
16201
|
library: args.library,
|
|
@@ -15561,28 +16230,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
15561
16230
|
if (!await fileExists2(logPath)) {
|
|
15562
16231
|
return { undone: false, message: "no apply found for library" };
|
|
15563
16232
|
}
|
|
15564
|
-
const raw = await
|
|
16233
|
+
const raw = await fs36.readFile(logPath, "utf8");
|
|
15565
16234
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
15566
16235
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
15567
16236
|
if (!match) {
|
|
15568
16237
|
return { undone: false, message: "no apply found for library" };
|
|
15569
16238
|
}
|
|
15570
|
-
const pkgPath =
|
|
16239
|
+
const pkgPath = path70.join(ctx.scanPath, "package.json");
|
|
15571
16240
|
if (await fileExists2(pkgPath)) {
|
|
15572
16241
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
15573
16242
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
15574
16243
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
15575
16244
|
pkg.dependencies = rest;
|
|
15576
|
-
await
|
|
16245
|
+
await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
15577
16246
|
}
|
|
15578
16247
|
}
|
|
15579
16248
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
15580
16249
|
for (const file of hookFiles) {
|
|
15581
|
-
const filePath =
|
|
15582
|
-
const content = await
|
|
16250
|
+
const filePath = path70.join(ctx.scanPath, file);
|
|
16251
|
+
const content = await fs36.readFile(filePath, "utf8");
|
|
15583
16252
|
if (content.includes(match.registration_snippet)) {
|
|
15584
16253
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
15585
|
-
await
|
|
16254
|
+
await fs36.writeFile(filePath, filtered, "utf8");
|
|
15586
16255
|
break;
|
|
15587
16256
|
}
|
|
15588
16257
|
}
|
|
@@ -15693,8 +16362,8 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
15693
16362
|
|
|
15694
16363
|
// src/connectors-config.ts
|
|
15695
16364
|
import os4 from "os";
|
|
15696
|
-
import
|
|
15697
|
-
import { promises as
|
|
16365
|
+
import path71 from "path";
|
|
16366
|
+
import { promises as fs37 } from "fs";
|
|
15698
16367
|
var CONNECTORS_CONFIG_VERSION = 1;
|
|
15699
16368
|
var EnvRefUnsetError = class extends Error {
|
|
15700
16369
|
ref;
|
|
@@ -15708,17 +16377,17 @@ var EnvRefUnsetError = class extends Error {
|
|
|
15708
16377
|
};
|
|
15709
16378
|
function neatHome2() {
|
|
15710
16379
|
const override = process.env.NEAT_HOME;
|
|
15711
|
-
if (override && override.length > 0) return
|
|
15712
|
-
return
|
|
16380
|
+
if (override && override.length > 0) return path71.resolve(override);
|
|
16381
|
+
return path71.join(os4.homedir(), ".neat");
|
|
15713
16382
|
}
|
|
15714
16383
|
function connectorsConfigPath(home = neatHome2()) {
|
|
15715
|
-
return
|
|
16384
|
+
return path71.join(home, "connectors.json");
|
|
15716
16385
|
}
|
|
15717
16386
|
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
15718
16387
|
async function warnIfModeLooserThan0600(file) {
|
|
15719
16388
|
if (process.platform === "win32") return;
|
|
15720
16389
|
try {
|
|
15721
|
-
const stat = await
|
|
16390
|
+
const stat = await fs37.stat(file);
|
|
15722
16391
|
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
15723
16392
|
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
15724
16393
|
console.warn(
|
|
@@ -15732,7 +16401,7 @@ async function readConnectorsConfig(home = neatHome2()) {
|
|
|
15732
16401
|
const file = connectorsConfigPath(home);
|
|
15733
16402
|
let raw;
|
|
15734
16403
|
try {
|
|
15735
|
-
raw = await
|
|
16404
|
+
raw = await fs37.readFile(file, "utf8");
|
|
15736
16405
|
} catch (err) {
|
|
15737
16406
|
if (err.code === "ENOENT") {
|
|
15738
16407
|
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
@@ -15843,7 +16512,7 @@ function connectorMatchesProject(entry, project) {
|
|
|
15843
16512
|
var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
|
|
15844
16513
|
var CONNECTORS_LOCK_RETRY_MS = 50;
|
|
15845
16514
|
function connectorsConfigLockPath(home = neatHome2()) {
|
|
15846
|
-
return
|
|
16515
|
+
return path71.join(home, "connectors.json.lock");
|
|
15847
16516
|
}
|
|
15848
16517
|
function isEnvRef(value) {
|
|
15849
16518
|
return value.length > 1 && value.startsWith("$");
|
|
@@ -15856,9 +16525,9 @@ function redactCredentialRef(ref) {
|
|
|
15856
16525
|
return out;
|
|
15857
16526
|
}
|
|
15858
16527
|
async function writeConfigAtomically0600(file, contents) {
|
|
15859
|
-
await
|
|
16528
|
+
await fs37.mkdir(path71.dirname(file), { recursive: true });
|
|
15860
16529
|
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
15861
|
-
const fd = await
|
|
16530
|
+
const fd = await fs37.open(tmp, "w", 384);
|
|
15862
16531
|
try {
|
|
15863
16532
|
await fd.writeFile(contents, "utf8");
|
|
15864
16533
|
await fd.chmod(384);
|
|
@@ -15866,14 +16535,14 @@ async function writeConfigAtomically0600(file, contents) {
|
|
|
15866
16535
|
} finally {
|
|
15867
16536
|
await fd.close();
|
|
15868
16537
|
}
|
|
15869
|
-
await
|
|
16538
|
+
await fs37.rename(tmp, file);
|
|
15870
16539
|
}
|
|
15871
16540
|
async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
|
|
15872
16541
|
const deadline = Date.now() + timeoutMs;
|
|
15873
|
-
await
|
|
16542
|
+
await fs37.mkdir(path71.dirname(lockPath), { recursive: true });
|
|
15874
16543
|
for (; ; ) {
|
|
15875
16544
|
try {
|
|
15876
|
-
const fd = await
|
|
16545
|
+
const fd = await fs37.open(lockPath, "wx");
|
|
15877
16546
|
try {
|
|
15878
16547
|
await fd.writeFile(`${process.pid}
|
|
15879
16548
|
`, "utf8");
|
|
@@ -15893,7 +16562,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
|
|
|
15893
16562
|
}
|
|
15894
16563
|
}
|
|
15895
16564
|
async function releaseConnectorsLock(lockPath) {
|
|
15896
|
-
await
|
|
16565
|
+
await fs37.unlink(lockPath).catch(() => {
|
|
15897
16566
|
});
|
|
15898
16567
|
}
|
|
15899
16568
|
async function withConnectorsLock(home, fn) {
|
|
@@ -16522,10 +17191,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
16522
17191
|
// src/connectors/supabase/map.ts
|
|
16523
17192
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
16524
17193
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
16525
|
-
function targetFromRestPath(
|
|
16526
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
17194
|
+
function targetFromRestPath(path72) {
|
|
17195
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path72);
|
|
16527
17196
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
16528
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
17197
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path72);
|
|
16529
17198
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
16530
17199
|
return null;
|
|
16531
17200
|
}
|
|
@@ -16634,21 +17303,21 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
16634
17303
|
}
|
|
16635
17304
|
|
|
16636
17305
|
// src/connectors/supabase/resolve.ts
|
|
16637
|
-
import { EdgeType as EdgeType26, infraId as
|
|
17306
|
+
import { EdgeType as EdgeType26, infraId as infraId21 } from "@neat.is/types";
|
|
16638
17307
|
function createSupabaseResolveTarget(graph, config) {
|
|
16639
17308
|
return (signal, _ctx) => {
|
|
16640
17309
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
16641
17310
|
return null;
|
|
16642
17311
|
}
|
|
16643
|
-
const subResourceId =
|
|
17312
|
+
const subResourceId = infraId21(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
16644
17313
|
if (graph.hasNode(subResourceId)) {
|
|
16645
17314
|
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
|
|
16646
17315
|
}
|
|
16647
|
-
const bareResourceId =
|
|
17316
|
+
const bareResourceId = infraId21(signal.targetKind, signal.targetName);
|
|
16648
17317
|
if (graph.hasNode(bareResourceId)) {
|
|
16649
17318
|
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
|
|
16650
17319
|
}
|
|
16651
|
-
const projectLevelId =
|
|
17320
|
+
const projectLevelId = infraId21("supabase", config.nodeRef);
|
|
16652
17321
|
if (graph.hasNode(projectLevelId)) {
|
|
16653
17322
|
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
|
|
16654
17323
|
}
|
|
@@ -17120,9 +17789,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
17120
17789
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
17121
17790
|
if (secondSep === -1) return null;
|
|
17122
17791
|
const method = rest.slice(0, secondSep);
|
|
17123
|
-
const
|
|
17124
|
-
if (!resourceName || !method || !
|
|
17125
|
-
return { resourceName, method, path:
|
|
17792
|
+
const path72 = rest.slice(secondSep + 1);
|
|
17793
|
+
if (!resourceName || !method || !path72) return null;
|
|
17794
|
+
return { resourceName, method, path: path72 };
|
|
17126
17795
|
}
|
|
17127
17796
|
function resourceNameFor(type, labels) {
|
|
17128
17797
|
if (!labels) return null;
|
|
@@ -17160,14 +17829,14 @@ function mapLogEntryToSignal(entry) {
|
|
|
17160
17829
|
if (!req) return null;
|
|
17161
17830
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
17162
17831
|
const method = req.requestMethod.toUpperCase();
|
|
17163
|
-
const
|
|
17164
|
-
if (
|
|
17832
|
+
const path72 = pathFromRequestUrl(req.requestUrl);
|
|
17833
|
+
if (path72 === null) return null;
|
|
17165
17834
|
const timestamp = entry.timestamp;
|
|
17166
17835
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
17167
17836
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
17168
17837
|
return {
|
|
17169
17838
|
targetKind: resourceType,
|
|
17170
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
17839
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path72 }),
|
|
17171
17840
|
callCount: 1,
|
|
17172
17841
|
errorCount: isError ? 1 : 0,
|
|
17173
17842
|
lastObservedIso: timestamp
|
|
@@ -17253,7 +17922,7 @@ function createFirebaseConnector(graph, serviceMap) {
|
|
|
17253
17922
|
}
|
|
17254
17923
|
|
|
17255
17924
|
// src/connectors/cloudflare/connector.ts
|
|
17256
|
-
import { EdgeType as EdgeType29, NodeType as NodeType35, fileId as fileId5, infraId as
|
|
17925
|
+
import { EdgeType as EdgeType29, NodeType as NodeType35, fileId as fileId5, infraId as infraId22 } from "@neat.is/types";
|
|
17257
17926
|
|
|
17258
17927
|
// src/connectors/cloudflare/client.ts
|
|
17259
17928
|
import { randomUUID } from "crypto";
|
|
@@ -17364,7 +18033,7 @@ function mapEventToSignal(event) {
|
|
|
17364
18033
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
17365
18034
|
const statusCode = metadata?.statusCode;
|
|
17366
18035
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
17367
|
-
const
|
|
18036
|
+
const path72 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
17368
18037
|
return {
|
|
17369
18038
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
17370
18039
|
targetName: scriptName,
|
|
@@ -17372,7 +18041,7 @@ function mapEventToSignal(event) {
|
|
|
17372
18041
|
errorCount: isError ? 1 : 0,
|
|
17373
18042
|
lastObservedIso: observedAt.toISOString(),
|
|
17374
18043
|
method,
|
|
17375
|
-
...
|
|
18044
|
+
...path72 ? { path: path72 } : {},
|
|
17376
18045
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
17377
18046
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
17378
18047
|
};
|
|
@@ -17418,8 +18087,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
17418
18087
|
});
|
|
17419
18088
|
return found;
|
|
17420
18089
|
}
|
|
17421
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
17422
|
-
const normalizedPath = normalizePathTemplate(
|
|
18090
|
+
function findMatchingRouteNode(graph, serviceName, method, path72) {
|
|
18091
|
+
const normalizedPath = normalizePathTemplate(path72);
|
|
17423
18092
|
let found = null;
|
|
17424
18093
|
graph.forEachNode((id, attrs) => {
|
|
17425
18094
|
if (found) return;
|
|
@@ -17436,10 +18105,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
17436
18105
|
return (signal) => {
|
|
17437
18106
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
17438
18107
|
const scriptName = signal.targetName;
|
|
17439
|
-
const { method, path:
|
|
18108
|
+
const { method, path: path72 } = signal;
|
|
17440
18109
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
17441
|
-
if (!method || !
|
|
17442
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
18110
|
+
if (!method || !path72) return wholeFileId;
|
|
18111
|
+
return findMatchingRouteNode(graph, serviceName, method, path72) ?? wholeFileId;
|
|
17443
18112
|
};
|
|
17444
18113
|
const mapping = config.workers?.[scriptName];
|
|
17445
18114
|
if (mapping) {
|
|
@@ -17460,7 +18129,7 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
17460
18129
|
};
|
|
17461
18130
|
}
|
|
17462
18131
|
return {
|
|
17463
|
-
targetNodeId:
|
|
18132
|
+
targetNodeId: infraId22("cloudflare-worker", scriptName),
|
|
17464
18133
|
serviceName: scriptName,
|
|
17465
18134
|
edgeType: EdgeType29.CALLS,
|
|
17466
18135
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
@@ -17644,12 +18313,12 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
17644
18313
|
}
|
|
17645
18314
|
|
|
17646
18315
|
// src/connectors/neon/resolve.ts
|
|
17647
|
-
import { EdgeType as EdgeType30, infraId as
|
|
18316
|
+
import { EdgeType as EdgeType30, infraId as infraId23 } from "@neat.is/types";
|
|
17648
18317
|
function createNeonResolveTarget(config) {
|
|
17649
18318
|
return (signal) => {
|
|
17650
18319
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
17651
18320
|
return {
|
|
17652
|
-
targetNodeId:
|
|
18321
|
+
targetNodeId: infraId23("sql-table", signal.targetName),
|
|
17653
18322
|
serviceName: config.serviceName,
|
|
17654
18323
|
edgeType: EdgeType30.CALLS,
|
|
17655
18324
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
@@ -17769,9 +18438,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
17769
18438
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
17770
18439
|
if (secondSep === -1) return null;
|
|
17771
18440
|
const method = rest.slice(0, secondSep);
|
|
17772
|
-
const
|
|
17773
|
-
if (!serviceName || !method || !
|
|
17774
|
-
return { serviceName, method, path:
|
|
18441
|
+
const path72 = rest.slice(secondSep + 1);
|
|
18442
|
+
if (!serviceName || !method || !path72) return null;
|
|
18443
|
+
return { serviceName, method, path: path72 };
|
|
17775
18444
|
}
|
|
17776
18445
|
|
|
17777
18446
|
// src/connectors/cloud-run/map.ts
|
|
@@ -17800,14 +18469,14 @@ function mapLogEntryToSignal2(entry) {
|
|
|
17800
18469
|
if (!req) return null;
|
|
17801
18470
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
17802
18471
|
const method = req.requestMethod.toUpperCase();
|
|
17803
|
-
const
|
|
17804
|
-
if (
|
|
18472
|
+
const path72 = pathFromRequestUrl2(req.requestUrl);
|
|
18473
|
+
if (path72 === null) return null;
|
|
17805
18474
|
const timestamp = entry.timestamp;
|
|
17806
18475
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
17807
18476
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
17808
18477
|
return {
|
|
17809
18478
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
17810
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
18479
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path72 }),
|
|
17811
18480
|
callCount: 1,
|
|
17812
18481
|
errorCount: isError ? 1 : 0,
|
|
17813
18482
|
lastObservedIso: timestamp
|
|
@@ -17823,7 +18492,7 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
17823
18492
|
}
|
|
17824
18493
|
|
|
17825
18494
|
// src/connectors/cloud-run/resolve.ts
|
|
17826
|
-
import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as
|
|
18495
|
+
import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as infraId24 } from "@neat.is/types";
|
|
17827
18496
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
17828
18497
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
17829
18498
|
let found = null;
|
|
@@ -17845,21 +18514,21 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
17845
18514
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
17846
18515
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
17847
18516
|
if (!identity) return null;
|
|
17848
|
-
const { serviceName: gcpServiceName, method, path:
|
|
18517
|
+
const { serviceName: gcpServiceName, method, path: path72 } = identity;
|
|
17849
18518
|
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
17850
18519
|
if (mappedService) {
|
|
17851
18520
|
const routeNodeId = findMatchingRouteNode2(
|
|
17852
18521
|
graph,
|
|
17853
18522
|
mappedService,
|
|
17854
18523
|
method,
|
|
17855
|
-
normalizePathTemplate(
|
|
18524
|
+
normalizePathTemplate(path72)
|
|
17856
18525
|
);
|
|
17857
18526
|
if (routeNodeId) {
|
|
17858
18527
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
|
|
17859
18528
|
}
|
|
17860
18529
|
}
|
|
17861
18530
|
return {
|
|
17862
|
-
targetNodeId:
|
|
18531
|
+
targetNodeId: infraId24(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
17863
18532
|
serviceName: mappedService ?? gcpServiceName,
|
|
17864
18533
|
edgeType: EdgeType31.CALLS,
|
|
17865
18534
|
ensureInfraNode: {
|
|
@@ -18191,17 +18860,17 @@ function mapInsightsToSignals(rows, observedAtIso) {
|
|
|
18191
18860
|
}
|
|
18192
18861
|
|
|
18193
18862
|
// src/connectors/planetscale/resolve.ts
|
|
18194
|
-
import { EdgeType as EdgeType33, infraId as
|
|
18863
|
+
import { EdgeType as EdgeType33, infraId as infraId25 } from "@neat.is/types";
|
|
18195
18864
|
var PLANETSCALE_DATABASE_KIND = "planetscale-database";
|
|
18196
18865
|
function createPlanetscaleResolveTarget(graph, config) {
|
|
18197
18866
|
const databaseName = `${config.organization}/${config.database}`;
|
|
18198
18867
|
return (signal, _ctx) => {
|
|
18199
18868
|
if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
18200
|
-
const tableId =
|
|
18869
|
+
const tableId = infraId25("sql-table", signal.targetName);
|
|
18201
18870
|
if (graph.hasNode(tableId)) {
|
|
18202
18871
|
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: EdgeType33.CALLS };
|
|
18203
18872
|
}
|
|
18204
|
-
const providerId =
|
|
18873
|
+
const providerId = infraId25(PLANETSCALE_DATABASE_KIND, databaseName);
|
|
18205
18874
|
return {
|
|
18206
18875
|
targetNodeId: providerId,
|
|
18207
18876
|
serviceName: config.serviceName,
|
|
@@ -20101,4 +20770,4 @@ export {
|
|
|
20101
20770
|
deprovisionConnector,
|
|
20102
20771
|
buildApi
|
|
20103
20772
|
};
|
|
20104
|
-
//# sourceMappingURL=chunk-
|
|
20773
|
+
//# sourceMappingURL=chunk-UN7VFA4H.js.map
|