@neat.is/core 0.9.0 → 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-RDLDSIEY.js → chunk-UN7VFA4H.js} +1112 -382
- package/dist/chunk-UN7VFA4H.js.map +1 -0
- package/dist/{chunk-WMYVJXUF.js → chunk-YKQB622D.js} +2 -2
- package/dist/cli.cjs +1648 -916
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +1311 -579
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +1327 -595
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +1214 -482
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-RDLDSIEY.js.map +0 -1
- /package/dist/{chunk-WMYVJXUF.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);
|
|
5558
|
+
edges.push(hop.edge);
|
|
5559
|
+
visited.add(hop.nextService);
|
|
5560
|
+
current = hop.nextService;
|
|
5561
|
+
}
|
|
5562
|
+
if (edges.length === 0) return null;
|
|
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);
|
|
5242
5607
|
edges.push(hop.edge);
|
|
5243
5608
|
visited.add(hop.nextService);
|
|
5244
5609
|
current = hop.nextService;
|
|
5245
5610
|
}
|
|
5246
5611
|
if (edges.length === 0) return null;
|
|
5247
|
-
return { path:
|
|
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,10 +10055,15 @@ 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;
|
|
10062
|
+
var SARAMA_IMPORT_RE = /"[^"]*\/sarama"/;
|
|
10063
|
+
var GO_PRODUCER_RE = /ProducerMessage\s*\{[\s\S]{0,300}?\bTopic\s*:\s*(?:"([^"]+)"|`([^`]+)`|([A-Za-z_]\w*))/g;
|
|
10064
|
+
var GO_CONSUMER_RE = /\.Consume\s*\([\s\S]{0,120}?\[\]string\s*\{([^}]*)\}/g;
|
|
10065
|
+
var GO_STRING_LITERAL_RE = /"([^"]+)"|`([^`]+)`/g;
|
|
10066
|
+
var GO_IDENT_RE = /[A-Za-z_]\w*/g;
|
|
9513
10067
|
function findAll(re, text) {
|
|
9514
10068
|
re.lastIndex = 0;
|
|
9515
10069
|
const out = [];
|
|
@@ -9519,37 +10073,93 @@ function findAll(re, text) {
|
|
|
9519
10073
|
}
|
|
9520
10074
|
return out;
|
|
9521
10075
|
}
|
|
10076
|
+
function resolveGoLiteral(ident, text) {
|
|
10077
|
+
const esc = ident.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10078
|
+
const re = new RegExp(
|
|
10079
|
+
`\\b${esc}(?:\\s+\\w[\\w.\\[\\]*]*)?\\s*(?::=|=)\\s*(?:"([^"]+)"|\`([^\`]+)\`)`
|
|
10080
|
+
);
|
|
10081
|
+
const m = re.exec(text);
|
|
10082
|
+
if (!m) return void 0;
|
|
10083
|
+
return m[1] ?? m[2];
|
|
10084
|
+
}
|
|
10085
|
+
function producerTopicAnchor(match) {
|
|
10086
|
+
const [full] = match;
|
|
10087
|
+
const tokenLen = match[1] ? match[1].length + 2 : match[2] ? match[2].length + 2 : match[3]?.length ?? 0;
|
|
10088
|
+
return match.index + full.length - tokenLen;
|
|
10089
|
+
}
|
|
10090
|
+
function goSaramaEndpoints(text, emit) {
|
|
10091
|
+
if (!SARAMA_IMPORT_RE.test(text)) return;
|
|
10092
|
+
GO_PRODUCER_RE.lastIndex = 0;
|
|
10093
|
+
let m;
|
|
10094
|
+
while ((m = GO_PRODUCER_RE.exec(text)) !== null) {
|
|
10095
|
+
const literal = m[1] ?? m[2];
|
|
10096
|
+
const anchor = producerTopicAnchor(m);
|
|
10097
|
+
if (literal) {
|
|
10098
|
+
emit(literal, "PUBLISHES_TO", anchor);
|
|
10099
|
+
} else if (m[3]) {
|
|
10100
|
+
const resolved = resolveGoLiteral(m[3], text);
|
|
10101
|
+
if (resolved) emit(resolved, "PUBLISHES_TO", anchor);
|
|
10102
|
+
}
|
|
10103
|
+
}
|
|
10104
|
+
GO_CONSUMER_RE.lastIndex = 0;
|
|
10105
|
+
while ((m = GO_CONSUMER_RE.exec(text)) !== null) {
|
|
10106
|
+
const inside = m[1] ?? "";
|
|
10107
|
+
const anchor = m.index + Math.max(0, m[0].indexOf("[]string"));
|
|
10108
|
+
let sawLiteral = false;
|
|
10109
|
+
GO_STRING_LITERAL_RE.lastIndex = 0;
|
|
10110
|
+
let s;
|
|
10111
|
+
while ((s = GO_STRING_LITERAL_RE.exec(inside)) !== null) {
|
|
10112
|
+
const topic = s[1] ?? s[2];
|
|
10113
|
+
if (topic) {
|
|
10114
|
+
emit(topic, "CONSUMES_FROM", anchor);
|
|
10115
|
+
sawLiteral = true;
|
|
10116
|
+
}
|
|
10117
|
+
}
|
|
10118
|
+
if (sawLiteral) continue;
|
|
10119
|
+
GO_IDENT_RE.lastIndex = 0;
|
|
10120
|
+
let id;
|
|
10121
|
+
while ((id = GO_IDENT_RE.exec(inside)) !== null) {
|
|
10122
|
+
const resolved = resolveGoLiteral(id[0], text);
|
|
10123
|
+
if (resolved) emit(resolved, "CONSUMES_FROM", anchor);
|
|
10124
|
+
}
|
|
10125
|
+
}
|
|
10126
|
+
}
|
|
9522
10127
|
function kafkaEndpointsFromFile(file, serviceDir) {
|
|
9523
10128
|
const out = [];
|
|
9524
10129
|
const seen = /* @__PURE__ */ new Set();
|
|
9525
|
-
const make = (topic, edgeType) => {
|
|
10130
|
+
const make = (topic, edgeType, anchor) => {
|
|
9526
10131
|
const key = `${edgeType}|${topic}`;
|
|
9527
10132
|
if (seen.has(key)) return;
|
|
9528
10133
|
seen.add(key);
|
|
9529
|
-
const line = lineOf(file.content, topic);
|
|
10134
|
+
const line = anchor !== void 0 ? file.content.slice(0, anchor).split("\n").length : lineOf(file.content, topic);
|
|
9530
10135
|
out.push({
|
|
9531
10136
|
infraId: infraId2("kafka-topic", topic),
|
|
9532
10137
|
name: topic,
|
|
9533
10138
|
kind: "kafka-topic",
|
|
9534
10139
|
edgeType,
|
|
9535
|
-
// `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})`
|
|
9536
|
-
//
|
|
9537
|
-
//
|
|
10140
|
+
// JS: `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})`
|
|
10141
|
+
// (kafkajs / node-rdkafka). Go: `sarama.ProducerMessage{Topic: 'x'}` /
|
|
10142
|
+
// `consumerGroup.Consume(ctx, []string{'x'}, …)`. Both are framework-aware
|
|
10143
|
+
// call sites — verified-call-site tier (ADR-066).
|
|
9538
10144
|
confidenceKind: "verified-call-site",
|
|
9539
10145
|
evidence: {
|
|
9540
|
-
file:
|
|
10146
|
+
file: path39.relative(serviceDir, file.path),
|
|
9541
10147
|
line,
|
|
9542
10148
|
snippet: snippet(file.content, line)
|
|
9543
10149
|
}
|
|
9544
10150
|
});
|
|
9545
10151
|
};
|
|
9546
|
-
|
|
9547
|
-
|
|
10152
|
+
if (path39.extname(file.path) === ".go") {
|
|
10153
|
+
goSaramaEndpoints(file.content, make);
|
|
10154
|
+
} else {
|
|
10155
|
+
for (const { topic } of findAll(PRODUCER_TOPIC_RE, file.content)) make(topic, "PUBLISHES_TO");
|
|
10156
|
+
for (const { topic } of findAll(CONSUMER_TOPIC_RE, file.content)) make(topic, "CONSUMES_FROM");
|
|
10157
|
+
}
|
|
9548
10158
|
return out;
|
|
9549
10159
|
}
|
|
9550
10160
|
|
|
9551
10161
|
// src/extract/calls/redis.ts
|
|
9552
|
-
import
|
|
10162
|
+
import path40 from "path";
|
|
9553
10163
|
import { infraId as infraId3 } from "@neat.is/types";
|
|
9554
10164
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
9555
10165
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -9572,7 +10182,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
9572
10182
|
// support tier (ADR-066).
|
|
9573
10183
|
confidenceKind: "url-with-structural-support",
|
|
9574
10184
|
evidence: {
|
|
9575
|
-
file:
|
|
10185
|
+
file: path40.relative(serviceDir, file.path),
|
|
9576
10186
|
line,
|
|
9577
10187
|
snippet: snippet(file.content, line)
|
|
9578
10188
|
}
|
|
@@ -9582,7 +10192,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
9582
10192
|
}
|
|
9583
10193
|
|
|
9584
10194
|
// src/extract/calls/aws.ts
|
|
9585
|
-
import
|
|
10195
|
+
import path41 from "path";
|
|
9586
10196
|
import { infraId as infraId4 } from "@neat.is/types";
|
|
9587
10197
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
9588
10198
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -9616,7 +10226,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
9616
10226
|
// (ADR-066).
|
|
9617
10227
|
confidenceKind: "verified-call-site",
|
|
9618
10228
|
evidence: {
|
|
9619
|
-
file:
|
|
10229
|
+
file: path41.relative(serviceDir, file.path),
|
|
9620
10230
|
line,
|
|
9621
10231
|
snippet: snippet(file.content, line)
|
|
9622
10232
|
}
|
|
@@ -9640,7 +10250,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
9640
10250
|
}
|
|
9641
10251
|
|
|
9642
10252
|
// src/extract/calls/grpc.ts
|
|
9643
|
-
import
|
|
10253
|
+
import path42 from "path";
|
|
9644
10254
|
import { infraId as infraId5 } from "@neat.is/types";
|
|
9645
10255
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
9646
10256
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
@@ -9699,7 +10309,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
9699
10309
|
// tier (ADR-066).
|
|
9700
10310
|
confidenceKind: "verified-call-site",
|
|
9701
10311
|
evidence: {
|
|
9702
|
-
file:
|
|
10312
|
+
file: path42.relative(serviceDir, file.path),
|
|
9703
10313
|
line,
|
|
9704
10314
|
snippet: snippet(file.content, line)
|
|
9705
10315
|
}
|
|
@@ -9709,7 +10319,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
9709
10319
|
}
|
|
9710
10320
|
|
|
9711
10321
|
// src/extract/calls/supabase.ts
|
|
9712
|
-
import
|
|
10322
|
+
import path43 from "path";
|
|
9713
10323
|
import { infraId as infraId6 } from "@neat.is/types";
|
|
9714
10324
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
9715
10325
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
@@ -9768,7 +10378,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
9768
10378
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
9769
10379
|
confidenceKind: "verified-call-site",
|
|
9770
10380
|
evidence: {
|
|
9771
|
-
file:
|
|
10381
|
+
file: path43.relative(serviceDir, file.path),
|
|
9772
10382
|
line,
|
|
9773
10383
|
snippet: snippet(file.content, line)
|
|
9774
10384
|
}
|
|
@@ -9795,7 +10405,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
9795
10405
|
edgeType: "CALLS",
|
|
9796
10406
|
confidenceKind: "verified-call-site",
|
|
9797
10407
|
evidence: {
|
|
9798
|
-
file:
|
|
10408
|
+
file: path43.relative(serviceDir, file.path),
|
|
9799
10409
|
line,
|
|
9800
10410
|
snippet: snippet(file.content, line)
|
|
9801
10411
|
}
|
|
@@ -9806,7 +10416,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
9806
10416
|
}
|
|
9807
10417
|
|
|
9808
10418
|
// src/extract/calls/firestore.ts
|
|
9809
|
-
import
|
|
10419
|
+
import path44 from "path";
|
|
9810
10420
|
import Parser8 from "tree-sitter";
|
|
9811
10421
|
import JavaScript6 from "tree-sitter-javascript";
|
|
9812
10422
|
import { infraId as infraId7 } from "@neat.is/types";
|
|
@@ -9845,7 +10455,7 @@ function isFirestoreClientFactory(node) {
|
|
|
9845
10455
|
}
|
|
9846
10456
|
function firestoreClientVars(root) {
|
|
9847
10457
|
const vars = /* @__PURE__ */ new Set();
|
|
9848
|
-
const
|
|
10458
|
+
const walk10 = (node) => {
|
|
9849
10459
|
if (node.type === "variable_declarator") {
|
|
9850
10460
|
const name = node.childForFieldName("name");
|
|
9851
10461
|
let value = node.childForFieldName("value");
|
|
@@ -9854,9 +10464,9 @@ function firestoreClientVars(root) {
|
|
|
9854
10464
|
vars.add(name.text);
|
|
9855
10465
|
}
|
|
9856
10466
|
}
|
|
9857
|
-
for (const c of namedChildren(node))
|
|
10467
|
+
for (const c of namedChildren(node)) walk10(c);
|
|
9858
10468
|
};
|
|
9859
|
-
|
|
10469
|
+
walk10(root);
|
|
9860
10470
|
return vars;
|
|
9861
10471
|
}
|
|
9862
10472
|
function isClientExpr(node, clientVars) {
|
|
@@ -9977,7 +10587,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
9977
10587
|
const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
|
|
9978
10588
|
if (!hasClient && !hasAdmin) return [];
|
|
9979
10589
|
const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
9980
|
-
const tree = parseSource3(parserForExt2(
|
|
10590
|
+
const tree = parseSource3(parserForExt2(path44.extname(file.path)), file.content);
|
|
9981
10591
|
const clientVars = firestoreClientVars(tree.rootNode);
|
|
9982
10592
|
const collLine = /* @__PURE__ */ new Map();
|
|
9983
10593
|
const writes = /* @__PURE__ */ new Map();
|
|
@@ -10011,7 +10621,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10011
10621
|
}
|
|
10012
10622
|
s.add(field);
|
|
10013
10623
|
};
|
|
10014
|
-
const
|
|
10624
|
+
const walk10 = (node) => {
|
|
10015
10625
|
if (node.type === "call_expression") {
|
|
10016
10626
|
const fn = node.childForFieldName("function");
|
|
10017
10627
|
const line = node.startPosition.row + 1;
|
|
@@ -10051,9 +10661,9 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10051
10661
|
}
|
|
10052
10662
|
}
|
|
10053
10663
|
}
|
|
10054
|
-
for (const c of namedChildren(node))
|
|
10664
|
+
for (const c of namedChildren(node)) walk10(c);
|
|
10055
10665
|
};
|
|
10056
|
-
|
|
10666
|
+
walk10(tree.rootNode);
|
|
10057
10667
|
const out = [];
|
|
10058
10668
|
for (const [collPath, line] of collLine) {
|
|
10059
10669
|
const byField = writes.get(collPath);
|
|
@@ -10081,7 +10691,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10081
10691
|
...columnSet.size > 0 ? { columns: [...columnSet] } : {},
|
|
10082
10692
|
...sdkWrites ? { sdkWrites } : {},
|
|
10083
10693
|
evidence: {
|
|
10084
|
-
file:
|
|
10694
|
+
file: path44.relative(serviceDir, file.path),
|
|
10085
10695
|
line,
|
|
10086
10696
|
snippet: snippet(file.content, line)
|
|
10087
10697
|
}
|
|
@@ -10091,7 +10701,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
10091
10701
|
}
|
|
10092
10702
|
|
|
10093
10703
|
// src/extract/calls/mongoose.ts
|
|
10094
|
-
import
|
|
10704
|
+
import path45 from "path";
|
|
10095
10705
|
import { infraId as infraId8 } from "@neat.is/types";
|
|
10096
10706
|
var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
|
|
10097
10707
|
var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
|
|
@@ -10242,7 +10852,7 @@ function endpoint(r, file, serviceDir, matchText) {
|
|
|
10242
10852
|
kind: r.kind,
|
|
10243
10853
|
edgeType: "CALLS",
|
|
10244
10854
|
confidenceKind: "verified-call-site",
|
|
10245
|
-
evidence: { file:
|
|
10855
|
+
evidence: { file: path45.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
10246
10856
|
};
|
|
10247
10857
|
}
|
|
10248
10858
|
function mongooseEndpointsFromFile(file, serviceDir) {
|
|
@@ -10332,7 +10942,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
10332
10942
|
const registry = /* @__PURE__ */ new Map();
|
|
10333
10943
|
for (const f of mongooseFiles) {
|
|
10334
10944
|
const fx = fileExportsOf(f.content, pluralizeOn);
|
|
10335
|
-
if (fx) registry.set(toPosix(
|
|
10945
|
+
if (fx) registry.set(toPosix(path45.relative(serviceDir, f.path)), fx);
|
|
10336
10946
|
}
|
|
10337
10947
|
if (registry.size === 0) return [];
|
|
10338
10948
|
const out = [];
|
|
@@ -10343,7 +10953,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
10343
10953
|
const directColl = /* @__PURE__ */ new Map();
|
|
10344
10954
|
const nsExports = /* @__PURE__ */ new Map();
|
|
10345
10955
|
for (const b of bindings) {
|
|
10346
|
-
const resolvedRel = await resolveJsImport(b.specifier,
|
|
10956
|
+
const resolvedRel = await resolveJsImport(b.specifier, path45.dirname(f.path), serviceDir, null);
|
|
10347
10957
|
if (!resolvedRel) continue;
|
|
10348
10958
|
const fx = registry.get(resolvedRel);
|
|
10349
10959
|
if (!fx) continue;
|
|
@@ -10386,7 +10996,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
10386
10996
|
}
|
|
10387
10997
|
|
|
10388
10998
|
// src/extract/calls/sqlalchemy.ts
|
|
10389
|
-
import
|
|
10999
|
+
import path46 from "path";
|
|
10390
11000
|
import Parser9 from "tree-sitter";
|
|
10391
11001
|
import Python5 from "tree-sitter-python";
|
|
10392
11002
|
import { infraId as infraId9 } from "@neat.is/types";
|
|
@@ -10525,7 +11135,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
|
|
|
10525
11135
|
childTable,
|
|
10526
11136
|
parentTable,
|
|
10527
11137
|
evidence: {
|
|
10528
|
-
file:
|
|
11138
|
+
file: path46.relative(serviceDir, file.path),
|
|
10529
11139
|
line,
|
|
10530
11140
|
snippet: snippet(file.content, line)
|
|
10531
11141
|
}
|
|
@@ -10550,7 +11160,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
|
10550
11160
|
confidenceKind: "verified-call-site",
|
|
10551
11161
|
...columns && columns.length > 0 ? { columns } : {},
|
|
10552
11162
|
evidence: {
|
|
10553
|
-
file:
|
|
11163
|
+
file: path46.relative(serviceDir, file.path),
|
|
10554
11164
|
line,
|
|
10555
11165
|
snippet: snippet(file.content, line)
|
|
10556
11166
|
}
|
|
@@ -10657,7 +11267,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
10657
11267
|
edgeType: "CALLS",
|
|
10658
11268
|
confidenceKind: "verified-call-site",
|
|
10659
11269
|
evidence: {
|
|
10660
|
-
file:
|
|
11270
|
+
file: path46.relative(serviceDir, file.path),
|
|
10661
11271
|
line,
|
|
10662
11272
|
snippet: snippet(file.content, line)
|
|
10663
11273
|
}
|
|
@@ -10668,7 +11278,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
10668
11278
|
}
|
|
10669
11279
|
|
|
10670
11280
|
// src/extract/calls/django-orm.ts
|
|
10671
|
-
import
|
|
11281
|
+
import path47 from "path";
|
|
10672
11282
|
import Parser10 from "tree-sitter";
|
|
10673
11283
|
import Python6 from "tree-sitter-python";
|
|
10674
11284
|
import { infraId as infraId10 } from "@neat.is/types";
|
|
@@ -10738,7 +11348,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
10738
11348
|
const tree = parseSource7(makePyParser4(), file.content);
|
|
10739
11349
|
const out = [];
|
|
10740
11350
|
const seen = /* @__PURE__ */ new Set();
|
|
10741
|
-
const defaultAppLabel =
|
|
11351
|
+
const defaultAppLabel = path47.basename(path47.dirname(file.path));
|
|
10742
11352
|
walk4(tree.rootNode, (node) => {
|
|
10743
11353
|
if (node.type !== "class_definition") return;
|
|
10744
11354
|
if (!extendsDjangoModel(node)) return;
|
|
@@ -10756,14 +11366,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
10756
11366
|
kind: "sql-table",
|
|
10757
11367
|
edgeType: "CALLS",
|
|
10758
11368
|
confidenceKind: "verified-call-site",
|
|
10759
|
-
evidence: { file:
|
|
11369
|
+
evidence: { file: path47.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
10760
11370
|
});
|
|
10761
11371
|
});
|
|
10762
11372
|
return out;
|
|
10763
11373
|
}
|
|
10764
11374
|
|
|
10765
11375
|
// src/extract/calls/drizzle.ts
|
|
10766
|
-
import
|
|
11376
|
+
import path48 from "path";
|
|
10767
11377
|
import Parser11 from "tree-sitter";
|
|
10768
11378
|
import JavaScript7 from "tree-sitter-javascript";
|
|
10769
11379
|
import { infraId as infraId11 } from "@neat.is/types";
|
|
@@ -10841,10 +11451,10 @@ function columnsFromObject(obj) {
|
|
|
10841
11451
|
}
|
|
10842
11452
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
10843
11453
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10844
|
-
const tree = parseSource3(parserForExt3(
|
|
11454
|
+
const tree = parseSource3(parserForExt3(path48.extname(file.path)), file.content);
|
|
10845
11455
|
const out = [];
|
|
10846
11456
|
const seen = /* @__PURE__ */ new Set();
|
|
10847
|
-
const
|
|
11457
|
+
const walk10 = (node) => {
|
|
10848
11458
|
if (node.type === "call_expression") {
|
|
10849
11459
|
const fn = node.childForFieldName("function");
|
|
10850
11460
|
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
@@ -10864,7 +11474,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
10864
11474
|
confidenceKind: "structural",
|
|
10865
11475
|
columns,
|
|
10866
11476
|
evidence: {
|
|
10867
|
-
file:
|
|
11477
|
+
file: path48.relative(serviceDir, file.path),
|
|
10868
11478
|
line,
|
|
10869
11479
|
snippet: snippet(file.content, line)
|
|
10870
11480
|
}
|
|
@@ -10872,9 +11482,9 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
10872
11482
|
}
|
|
10873
11483
|
}
|
|
10874
11484
|
}
|
|
10875
|
-
for (const c of namedChildren4(node))
|
|
11485
|
+
for (const c of namedChildren4(node)) walk10(c);
|
|
10876
11486
|
};
|
|
10877
|
-
|
|
11487
|
+
walk10(tree.rootNode);
|
|
10878
11488
|
return out;
|
|
10879
11489
|
}
|
|
10880
11490
|
function enclosingVarName(call) {
|
|
@@ -10896,7 +11506,7 @@ function enclosingVarName(call) {
|
|
|
10896
11506
|
function collectDrizzleTables(root) {
|
|
10897
11507
|
const tables = [];
|
|
10898
11508
|
const varToTable = /* @__PURE__ */ new Map();
|
|
10899
|
-
const
|
|
11509
|
+
const walk10 = (node) => {
|
|
10900
11510
|
if (node.type === "call_expression") {
|
|
10901
11511
|
const fn = node.childForFieldName("function");
|
|
10902
11512
|
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
@@ -10911,9 +11521,9 @@ function collectDrizzleTables(root) {
|
|
|
10911
11521
|
}
|
|
10912
11522
|
}
|
|
10913
11523
|
}
|
|
10914
|
-
for (const c of namedChildren4(node))
|
|
11524
|
+
for (const c of namedChildren4(node)) walk10(c);
|
|
10915
11525
|
};
|
|
10916
|
-
|
|
11526
|
+
walk10(root);
|
|
10917
11527
|
return { tables, varToTable };
|
|
10918
11528
|
}
|
|
10919
11529
|
function referencesTargetVar(call) {
|
|
@@ -10930,13 +11540,13 @@ function referencesTargetVar(call) {
|
|
|
10930
11540
|
}
|
|
10931
11541
|
function drizzleForeignKeys(file, serviceDir) {
|
|
10932
11542
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10933
|
-
const tree = parseSource3(parserForExt3(
|
|
11543
|
+
const tree = parseSource3(parserForExt3(path48.extname(file.path)), file.content);
|
|
10934
11544
|
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
10935
11545
|
const out = [];
|
|
10936
11546
|
const seen = /* @__PURE__ */ new Set();
|
|
10937
11547
|
for (const table of tables) {
|
|
10938
11548
|
if (!table.object) continue;
|
|
10939
|
-
const
|
|
11549
|
+
const walk10 = (node) => {
|
|
10940
11550
|
if (node.type === "call_expression") {
|
|
10941
11551
|
const targetVar = referencesTargetVar(node);
|
|
10942
11552
|
const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
|
|
@@ -10949,7 +11559,7 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
10949
11559
|
childTable: table.tableName,
|
|
10950
11560
|
parentTable,
|
|
10951
11561
|
evidence: {
|
|
10952
|
-
file:
|
|
11562
|
+
file: path48.relative(serviceDir, file.path),
|
|
10953
11563
|
line,
|
|
10954
11564
|
snippet: snippet(file.content, line)
|
|
10955
11565
|
}
|
|
@@ -10957,15 +11567,15 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
10957
11567
|
}
|
|
10958
11568
|
}
|
|
10959
11569
|
}
|
|
10960
|
-
for (const c of namedChildren4(node))
|
|
11570
|
+
for (const c of namedChildren4(node)) walk10(c);
|
|
10961
11571
|
};
|
|
10962
|
-
|
|
11572
|
+
walk10(table.object);
|
|
10963
11573
|
}
|
|
10964
11574
|
return out;
|
|
10965
11575
|
}
|
|
10966
11576
|
|
|
10967
11577
|
// src/extract/calls/prisma.ts
|
|
10968
|
-
import
|
|
11578
|
+
import path49 from "path";
|
|
10969
11579
|
import { infraId as infraId12 } from "@neat.is/types";
|
|
10970
11580
|
var SCALAR_TYPES = /* @__PURE__ */ new Set([
|
|
10971
11581
|
"Int",
|
|
@@ -11021,7 +11631,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
11021
11631
|
confidenceKind: "structural",
|
|
11022
11632
|
columns: b.columns,
|
|
11023
11633
|
evidence: {
|
|
11024
|
-
file:
|
|
11634
|
+
file: path49.relative(serviceDir, file.path),
|
|
11025
11635
|
line: b.startLine,
|
|
11026
11636
|
snippet: snippet(content, b.startLine)
|
|
11027
11637
|
}
|
|
@@ -11082,7 +11692,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
11082
11692
|
}
|
|
11083
11693
|
async function prismaColumnEndpoints(serviceDir) {
|
|
11084
11694
|
const schemaPath = await findFirst(serviceDir, [
|
|
11085
|
-
|
|
11695
|
+
path49.join("prisma", "schema.prisma"),
|
|
11086
11696
|
"schema.prisma"
|
|
11087
11697
|
]);
|
|
11088
11698
|
if (!schemaPath) return [];
|
|
@@ -11157,7 +11767,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
11157
11767
|
childTable: current.table,
|
|
11158
11768
|
parentTable,
|
|
11159
11769
|
evidence: {
|
|
11160
|
-
file:
|
|
11770
|
+
file: path49.relative(serviceDir, file.path),
|
|
11161
11771
|
line: lineNo,
|
|
11162
11772
|
snippet: snippet(content, lineNo)
|
|
11163
11773
|
}
|
|
@@ -11172,7 +11782,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
11172
11782
|
}
|
|
11173
11783
|
async function prismaForeignKeys(serviceDir) {
|
|
11174
11784
|
const schemaPath = await findFirst(serviceDir, [
|
|
11175
|
-
|
|
11785
|
+
path49.join("prisma", "schema.prisma"),
|
|
11176
11786
|
"schema.prisma"
|
|
11177
11787
|
]);
|
|
11178
11788
|
if (!schemaPath) return [];
|
|
@@ -11182,7 +11792,7 @@ async function prismaForeignKeys(serviceDir) {
|
|
|
11182
11792
|
}
|
|
11183
11793
|
|
|
11184
11794
|
// src/extract/calls/activerecord.ts
|
|
11185
|
-
import
|
|
11795
|
+
import path50 from "path";
|
|
11186
11796
|
import Parser12 from "tree-sitter";
|
|
11187
11797
|
import Ruby3 from "tree-sitter-ruby";
|
|
11188
11798
|
import { infraId as infraId13 } from "@neat.is/types";
|
|
@@ -11390,7 +12000,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
|
|
|
11390
12000
|
confidenceKind: "structural",
|
|
11391
12001
|
...table.columns.length > 0 ? { columns: table.columns } : {},
|
|
11392
12002
|
evidence: {
|
|
11393
|
-
file:
|
|
12003
|
+
file: path50.relative(serviceDir, file.path),
|
|
11394
12004
|
line: table.line,
|
|
11395
12005
|
snippet: snippet(file.content, table.line)
|
|
11396
12006
|
}
|
|
@@ -11412,7 +12022,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
|
|
|
11412
12022
|
childTable,
|
|
11413
12023
|
parentTable,
|
|
11414
12024
|
evidence: {
|
|
11415
|
-
file:
|
|
12025
|
+
file: path50.relative(serviceDir, file.path),
|
|
11416
12026
|
line,
|
|
11417
12027
|
snippet: snippet(file.content, line)
|
|
11418
12028
|
}
|
|
@@ -11495,7 +12105,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
|
|
|
11495
12105
|
edgeType: "CALLS",
|
|
11496
12106
|
confidenceKind: "verified-call-site",
|
|
11497
12107
|
evidence: {
|
|
11498
|
-
file:
|
|
12108
|
+
file: path50.relative(serviceDir, file.path),
|
|
11499
12109
|
line,
|
|
11500
12110
|
snippet: snippet(file.content, line)
|
|
11501
12111
|
}
|
|
@@ -11532,7 +12142,7 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
11532
12142
|
childTable,
|
|
11533
12143
|
parentTable,
|
|
11534
12144
|
evidence: {
|
|
11535
|
-
file:
|
|
12145
|
+
file: path50.relative(serviceDir, file.path),
|
|
11536
12146
|
line,
|
|
11537
12147
|
snippet: snippet(file.content, line)
|
|
11538
12148
|
}
|
|
@@ -11543,7 +12153,7 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
11543
12153
|
}
|
|
11544
12154
|
|
|
11545
12155
|
// src/extract/calls/eloquent.ts
|
|
11546
|
-
import
|
|
12156
|
+
import path51 from "path";
|
|
11547
12157
|
import Parser13 from "tree-sitter";
|
|
11548
12158
|
import Php3 from "tree-sitter-php";
|
|
11549
12159
|
import { infraId as infraId14 } from "@neat.is/types";
|
|
@@ -11800,7 +12410,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
|
|
|
11800
12410
|
confidenceKind: "structural",
|
|
11801
12411
|
...columns.length > 0 ? { columns } : {},
|
|
11802
12412
|
evidence: {
|
|
11803
|
-
file:
|
|
12413
|
+
file: path51.relative(serviceDir, file.path),
|
|
11804
12414
|
line: bp.line,
|
|
11805
12415
|
snippet: snippet(file.content, bp.line)
|
|
11806
12416
|
}
|
|
@@ -11822,7 +12432,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
|
|
|
11822
12432
|
childTable,
|
|
11823
12433
|
parentTable,
|
|
11824
12434
|
evidence: {
|
|
11825
|
-
file:
|
|
12435
|
+
file: path51.relative(serviceDir, file.path),
|
|
11826
12436
|
line,
|
|
11827
12437
|
snippet: snippet(file.content, line)
|
|
11828
12438
|
}
|
|
@@ -11942,7 +12552,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
|
|
|
11942
12552
|
edgeType: "CALLS",
|
|
11943
12553
|
confidenceKind: "verified-call-site",
|
|
11944
12554
|
evidence: {
|
|
11945
|
-
file:
|
|
12555
|
+
file: path51.relative(serviceDir, file.path),
|
|
11946
12556
|
line,
|
|
11947
12557
|
snippet: snippet(file.content, line)
|
|
11948
12558
|
}
|
|
@@ -11978,7 +12588,7 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
11978
12588
|
childTable,
|
|
11979
12589
|
parentTable,
|
|
11980
12590
|
evidence: {
|
|
11981
|
-
file:
|
|
12591
|
+
file: path51.relative(serviceDir, file.path),
|
|
11982
12592
|
line,
|
|
11983
12593
|
snippet: snippet(file.content, line)
|
|
11984
12594
|
}
|
|
@@ -11989,7 +12599,7 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
11989
12599
|
}
|
|
11990
12600
|
|
|
11991
12601
|
// src/extract/calls/go.ts
|
|
11992
|
-
import
|
|
12602
|
+
import path52 from "path";
|
|
11993
12603
|
import Parser14 from "tree-sitter";
|
|
11994
12604
|
import Go4 from "tree-sitter-go";
|
|
11995
12605
|
import { infraId as infraId15 } from "@neat.is/types";
|
|
@@ -12063,7 +12673,7 @@ function firstStringLiteralArg(argsNode) {
|
|
|
12063
12673
|
return null;
|
|
12064
12674
|
}
|
|
12065
12675
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
12066
|
-
if (
|
|
12676
|
+
if (path52.extname(file.path) !== ".go") return [];
|
|
12067
12677
|
if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
|
|
12068
12678
|
const tree = parseSource10(makeGoParser3(), file.content);
|
|
12069
12679
|
const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
|
|
@@ -12092,7 +12702,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
12092
12702
|
confidenceKind: "verified-call-site",
|
|
12093
12703
|
...columns.length > 0 ? { columns } : {},
|
|
12094
12704
|
evidence: {
|
|
12095
|
-
file: toPosix(
|
|
12705
|
+
file: toPosix(path52.relative(serviceDir, file.path)),
|
|
12096
12706
|
line,
|
|
12097
12707
|
snippet: snippet(file.content, line)
|
|
12098
12708
|
}
|
|
@@ -12102,7 +12712,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
12102
12712
|
}
|
|
12103
12713
|
|
|
12104
12714
|
// src/extract/calls/gorm.ts
|
|
12105
|
-
import
|
|
12715
|
+
import path53 from "path";
|
|
12106
12716
|
import Parser15 from "tree-sitter";
|
|
12107
12717
|
import Go5 from "tree-sitter-go";
|
|
12108
12718
|
import { infraId as infraId16 } from "@neat.is/types";
|
|
@@ -12535,7 +13145,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
12535
13145
|
seen.delete(struct.name);
|
|
12536
13146
|
}
|
|
12537
13147
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
12538
|
-
if (
|
|
13148
|
+
if (path53.extname(file.path) !== ".go") return [];
|
|
12539
13149
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
12540
13150
|
const tree = parseSource11(makeGoParser4(), file.content);
|
|
12541
13151
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -12557,7 +13167,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
12557
13167
|
confidenceKind: "structural",
|
|
12558
13168
|
...columns.length > 0 ? { columns } : {},
|
|
12559
13169
|
evidence: {
|
|
12560
|
-
file: toPosix(
|
|
13170
|
+
file: toPosix(path53.relative(serviceDir, file.path)),
|
|
12561
13171
|
line: struct.line,
|
|
12562
13172
|
snippet: snippet(file.content, struct.line)
|
|
12563
13173
|
}
|
|
@@ -12566,7 +13176,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
12566
13176
|
return out;
|
|
12567
13177
|
}
|
|
12568
13178
|
function gormForeignKeys(file, serviceDir) {
|
|
12569
|
-
if (
|
|
13179
|
+
if (path53.extname(file.path) !== ".go") return [];
|
|
12570
13180
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
12571
13181
|
const tree = parseSource11(makeGoParser4(), file.content);
|
|
12572
13182
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -12581,7 +13191,7 @@ function gormForeignKeys(file, serviceDir) {
|
|
|
12581
13191
|
childTable,
|
|
12582
13192
|
parentTable,
|
|
12583
13193
|
evidence: {
|
|
12584
|
-
file: toPosix(
|
|
13194
|
+
file: toPosix(path53.relative(serviceDir, file.path)),
|
|
12585
13195
|
line,
|
|
12586
13196
|
snippet: snippet(file.content, line)
|
|
12587
13197
|
}
|
|
@@ -12616,6 +13226,121 @@ function gormForeignKeys(file, serviceDir) {
|
|
|
12616
13226
|
return out;
|
|
12617
13227
|
}
|
|
12618
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
|
+
|
|
12619
13344
|
// src/extract/calls/index.ts
|
|
12620
13345
|
function edgeTypeFromEndpoint(ep) {
|
|
12621
13346
|
switch (ep.edgeType) {
|
|
@@ -12674,6 +13399,11 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
12674
13399
|
} catch (err) {
|
|
12675
13400
|
recordExtractionError("laravel eloquent extraction", file.path, err);
|
|
12676
13401
|
}
|
|
13402
|
+
try {
|
|
13403
|
+
endpoints.push(...efcoreEndpointsFromFile(file, service.dir));
|
|
13404
|
+
} catch (err) {
|
|
13405
|
+
recordExtractionError("efcore data-axis extraction", file.path, err);
|
|
13406
|
+
}
|
|
12677
13407
|
}
|
|
12678
13408
|
endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
|
|
12679
13409
|
endpoints.push(...pythonOrmCrossFileEndpoints(maskedFiles, service.dir));
|
|
@@ -12777,7 +13507,7 @@ import {
|
|
|
12777
13507
|
Provenance as Provenance16,
|
|
12778
13508
|
confidenceForExtracted as confidenceForExtracted13,
|
|
12779
13509
|
extractedEdgeId as extractedEdgeId10,
|
|
12780
|
-
infraId as
|
|
13510
|
+
infraId as infraId18
|
|
12781
13511
|
} from "@neat.is/types";
|
|
12782
13512
|
async function addTableEdges(graph, services) {
|
|
12783
13513
|
let nodesAdded = 0;
|
|
@@ -12806,8 +13536,8 @@ async function addTableEdges(graph, services) {
|
|
|
12806
13536
|
}
|
|
12807
13537
|
refs.push(...modelRefs);
|
|
12808
13538
|
for (const ref of refs) {
|
|
12809
|
-
const childId =
|
|
12810
|
-
const parentId =
|
|
13539
|
+
const childId = infraId18("sql-table", ref.childTable);
|
|
13540
|
+
const parentId = infraId18("sql-table", ref.parentTable);
|
|
12811
13541
|
if (childId === parentId) continue;
|
|
12812
13542
|
nodesAdded += ensureTableNode(graph, childId, ref.childTable);
|
|
12813
13543
|
nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
|
|
@@ -12842,14 +13572,14 @@ function ensureTableNode(graph, id, name) {
|
|
|
12842
13572
|
}
|
|
12843
13573
|
|
|
12844
13574
|
// src/extract/infra/docker-compose.ts
|
|
12845
|
-
import
|
|
13575
|
+
import path55 from "path";
|
|
12846
13576
|
import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
|
|
12847
13577
|
|
|
12848
13578
|
// src/extract/infra/shared.ts
|
|
12849
|
-
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";
|
|
12850
13580
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
12851
13581
|
return {
|
|
12852
|
-
id:
|
|
13582
|
+
id: infraId19(kind, name),
|
|
12853
13583
|
type: NodeType26.InfraNode,
|
|
12854
13584
|
name,
|
|
12855
13585
|
provider,
|
|
@@ -12912,7 +13642,7 @@ function dependsOnList(value) {
|
|
|
12912
13642
|
}
|
|
12913
13643
|
function serviceNameToServiceNode(name, services) {
|
|
12914
13644
|
for (const s of services) {
|
|
12915
|
-
if (s.node.name === name ||
|
|
13645
|
+
if (s.node.name === name || path55.basename(s.dir) === name) return s.node.id;
|
|
12916
13646
|
}
|
|
12917
13647
|
return null;
|
|
12918
13648
|
}
|
|
@@ -12921,7 +13651,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
12921
13651
|
let edgesAdded = 0;
|
|
12922
13652
|
let composePath = null;
|
|
12923
13653
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
12924
|
-
const abs =
|
|
13654
|
+
const abs = path55.join(scanPath, name);
|
|
12925
13655
|
if (await exists(abs)) {
|
|
12926
13656
|
composePath = abs;
|
|
12927
13657
|
break;
|
|
@@ -12934,13 +13664,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
12934
13664
|
} catch (err) {
|
|
12935
13665
|
recordExtractionError(
|
|
12936
13666
|
"infra docker-compose",
|
|
12937
|
-
|
|
13667
|
+
path55.relative(scanPath, composePath),
|
|
12938
13668
|
err
|
|
12939
13669
|
);
|
|
12940
13670
|
return { nodesAdded, edgesAdded };
|
|
12941
13671
|
}
|
|
12942
13672
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
12943
|
-
const evidenceFile =
|
|
13673
|
+
const evidenceFile = path55.relative(scanPath, composePath).split(path55.sep).join("/");
|
|
12944
13674
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
12945
13675
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
12946
13676
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -12981,8 +13711,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
12981
13711
|
}
|
|
12982
13712
|
|
|
12983
13713
|
// src/extract/infra/dockerfile.ts
|
|
12984
|
-
import
|
|
12985
|
-
import { promises as
|
|
13714
|
+
import path56 from "path";
|
|
13715
|
+
import { promises as fs25 } from "fs";
|
|
12986
13716
|
import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
|
|
12987
13717
|
function readDockerfile(content) {
|
|
12988
13718
|
let image = null;
|
|
@@ -13012,15 +13742,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
13012
13742
|
let nodesAdded = 0;
|
|
13013
13743
|
let edgesAdded = 0;
|
|
13014
13744
|
for (const service of services) {
|
|
13015
|
-
const dockerfilePath =
|
|
13745
|
+
const dockerfilePath = path56.join(service.dir, "Dockerfile");
|
|
13016
13746
|
if (!await exists(dockerfilePath)) continue;
|
|
13017
13747
|
let content;
|
|
13018
13748
|
try {
|
|
13019
|
-
content = await
|
|
13749
|
+
content = await fs25.readFile(dockerfilePath, "utf8");
|
|
13020
13750
|
} catch (err) {
|
|
13021
13751
|
recordExtractionError(
|
|
13022
13752
|
"infra dockerfile",
|
|
13023
|
-
|
|
13753
|
+
path56.relative(scanPath, dockerfilePath),
|
|
13024
13754
|
err
|
|
13025
13755
|
);
|
|
13026
13756
|
continue;
|
|
@@ -13032,8 +13762,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
13032
13762
|
graph.addNode(node.id, node);
|
|
13033
13763
|
nodesAdded++;
|
|
13034
13764
|
}
|
|
13035
|
-
const relDockerfile = toPosix(
|
|
13036
|
-
const evidenceFile = toPosix(
|
|
13765
|
+
const relDockerfile = toPosix(path56.relative(service.dir, dockerfilePath));
|
|
13766
|
+
const evidenceFile = toPosix(path56.relative(scanPath, dockerfilePath));
|
|
13037
13767
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
13038
13768
|
graph,
|
|
13039
13769
|
service.pkg.name,
|
|
@@ -13084,23 +13814,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
13084
13814
|
}
|
|
13085
13815
|
|
|
13086
13816
|
// src/extract/infra/terraform.ts
|
|
13087
|
-
import { promises as
|
|
13088
|
-
import
|
|
13817
|
+
import { promises as fs26 } from "fs";
|
|
13818
|
+
import path57 from "path";
|
|
13089
13819
|
import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
|
|
13090
13820
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
13091
13821
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
13092
13822
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
13093
13823
|
if (depth > max) return [];
|
|
13094
13824
|
const out = [];
|
|
13095
|
-
const entries = await
|
|
13825
|
+
const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
13096
13826
|
for (const entry of entries) {
|
|
13097
13827
|
if (entry.isDirectory()) {
|
|
13098
13828
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
13099
|
-
const child =
|
|
13829
|
+
const child = path57.join(start, entry.name);
|
|
13100
13830
|
if (await isPythonVenvDir(child)) continue;
|
|
13101
13831
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
13102
13832
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
13103
|
-
out.push(
|
|
13833
|
+
out.push(path57.join(start, entry.name));
|
|
13104
13834
|
}
|
|
13105
13835
|
}
|
|
13106
13836
|
return out;
|
|
@@ -13131,8 +13861,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
13131
13861
|
let edgesAdded = 0;
|
|
13132
13862
|
const files = await walkTfFiles(scanPath);
|
|
13133
13863
|
for (const file of files) {
|
|
13134
|
-
const content = await
|
|
13135
|
-
const evidenceFile = toPosix(
|
|
13864
|
+
const content = await fs26.readFile(file, "utf8");
|
|
13865
|
+
const evidenceFile = toPosix(path57.relative(scanPath, file));
|
|
13136
13866
|
const resources = [];
|
|
13137
13867
|
const byKey = /* @__PURE__ */ new Map();
|
|
13138
13868
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -13188,8 +13918,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
13188
13918
|
}
|
|
13189
13919
|
|
|
13190
13920
|
// src/extract/infra/k8s.ts
|
|
13191
|
-
import { promises as
|
|
13192
|
-
import
|
|
13921
|
+
import { promises as fs27 } from "fs";
|
|
13922
|
+
import path58 from "path";
|
|
13193
13923
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
13194
13924
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
13195
13925
|
Service: "k8s-service",
|
|
@@ -13203,15 +13933,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
13203
13933
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
13204
13934
|
if (depth > max) return [];
|
|
13205
13935
|
const out = [];
|
|
13206
|
-
const entries = await
|
|
13936
|
+
const entries = await fs27.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
13207
13937
|
for (const entry of entries) {
|
|
13208
13938
|
if (entry.isDirectory()) {
|
|
13209
13939
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
13210
|
-
const child =
|
|
13940
|
+
const child = path58.join(start, entry.name);
|
|
13211
13941
|
if (await isPythonVenvDir(child)) continue;
|
|
13212
13942
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
13213
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
13214
|
-
out.push(
|
|
13943
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path58.extname(entry.name))) {
|
|
13944
|
+
out.push(path58.join(start, entry.name));
|
|
13215
13945
|
}
|
|
13216
13946
|
}
|
|
13217
13947
|
return out;
|
|
@@ -13220,7 +13950,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
13220
13950
|
let nodesAdded = 0;
|
|
13221
13951
|
const files = await walkYamlFiles2(scanPath);
|
|
13222
13952
|
for (const file of files) {
|
|
13223
|
-
const content = await
|
|
13953
|
+
const content = await fs27.readFile(file, "utf8");
|
|
13224
13954
|
let docs;
|
|
13225
13955
|
try {
|
|
13226
13956
|
docs = parseAllDocuments2(content).map((d) => d.toJSON());
|
|
@@ -13243,16 +13973,16 @@ async function addK8sResources(graph, scanPath) {
|
|
|
13243
13973
|
}
|
|
13244
13974
|
|
|
13245
13975
|
// src/extract/infra/cloudflare.ts
|
|
13246
|
-
import { promises as
|
|
13247
|
-
import
|
|
13976
|
+
import { promises as fs28 } from "fs";
|
|
13977
|
+
import path59 from "path";
|
|
13248
13978
|
import { parse as parseToml3 } from "smol-toml";
|
|
13249
13979
|
import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
|
|
13250
13980
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
13251
13981
|
async function readWranglerConfig(dir) {
|
|
13252
13982
|
for (const filename of WRANGLER_FILENAMES) {
|
|
13253
|
-
const abs =
|
|
13983
|
+
const abs = path59.join(dir, filename);
|
|
13254
13984
|
if (!await exists(abs)) continue;
|
|
13255
|
-
const raw = await
|
|
13985
|
+
const raw = await fs28.readFile(abs, "utf8");
|
|
13256
13986
|
const config = filename === "wrangler.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
13257
13987
|
return { config, relFile: filename, raw };
|
|
13258
13988
|
}
|
|
@@ -13313,11 +14043,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
13313
14043
|
try {
|
|
13314
14044
|
read = await readWranglerConfig(service.dir);
|
|
13315
14045
|
} catch (err) {
|
|
13316
|
-
recordExtractionError("infra cloudflare",
|
|
14046
|
+
recordExtractionError("infra cloudflare", path59.relative(scanPath, service.dir), err);
|
|
13317
14047
|
continue;
|
|
13318
14048
|
}
|
|
13319
14049
|
if (!read || !read.config.name) continue;
|
|
13320
|
-
const evidenceFile = toPosix(
|
|
14050
|
+
const evidenceFile = toPosix(path59.relative(scanPath, path59.join(service.dir, read.relFile)));
|
|
13321
14051
|
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
13322
14052
|
}
|
|
13323
14053
|
for (const worker of discovered) {
|
|
@@ -13329,7 +14059,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
13329
14059
|
}
|
|
13330
14060
|
let anchorId = service.node.id;
|
|
13331
14061
|
if (config.main) {
|
|
13332
|
-
const entryRelPath = toPosix(
|
|
14062
|
+
const entryRelPath = toPosix(path59.normalize(config.main));
|
|
13333
14063
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
13334
14064
|
graph,
|
|
13335
14065
|
service.pkg.name,
|
|
@@ -13475,24 +14205,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
13475
14205
|
}
|
|
13476
14206
|
|
|
13477
14207
|
// src/extract/infra/vercel.ts
|
|
13478
|
-
import { promises as
|
|
13479
|
-
import
|
|
14208
|
+
import { promises as fs29 } from "fs";
|
|
14209
|
+
import path60 from "path";
|
|
13480
14210
|
import { EdgeType as EdgeType21 } from "@neat.is/types";
|
|
13481
14211
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
13482
14212
|
async function readVercelConfig(dir) {
|
|
13483
14213
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
13484
|
-
const abs =
|
|
14214
|
+
const abs = path60.join(dir, filename);
|
|
13485
14215
|
if (!await exists(abs)) continue;
|
|
13486
|
-
const raw = await
|
|
14216
|
+
const raw = await fs29.readFile(abs, "utf8");
|
|
13487
14217
|
const config = JSON.parse(maskCommentsInSource(raw));
|
|
13488
14218
|
return { config, relFile: filename, raw };
|
|
13489
14219
|
}
|
|
13490
14220
|
return null;
|
|
13491
14221
|
}
|
|
13492
14222
|
async function readLinkedProjectName(dir) {
|
|
13493
|
-
const abs =
|
|
14223
|
+
const abs = path60.join(dir, ".vercel", "project.json");
|
|
13494
14224
|
if (!await exists(abs)) return void 0;
|
|
13495
|
-
const parsed = JSON.parse(await
|
|
14225
|
+
const parsed = JSON.parse(await fs29.readFile(abs, "utf8"));
|
|
13496
14226
|
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
13497
14227
|
}
|
|
13498
14228
|
function routeSource(route) {
|
|
@@ -13508,7 +14238,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
13508
14238
|
read = await readVercelConfig(service.dir);
|
|
13509
14239
|
projectName = await readLinkedProjectName(service.dir);
|
|
13510
14240
|
} catch (err) {
|
|
13511
|
-
recordExtractionError("infra vercel",
|
|
14241
|
+
recordExtractionError("infra vercel", path60.relative(scanPath, service.dir), err);
|
|
13512
14242
|
continue;
|
|
13513
14243
|
}
|
|
13514
14244
|
if (!read && !projectName) continue;
|
|
@@ -13524,7 +14254,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
13524
14254
|
const anchorId = service.node.id;
|
|
13525
14255
|
if (!read) continue;
|
|
13526
14256
|
const { config, relFile, raw } = read;
|
|
13527
|
-
const evidenceFile = toPosix(
|
|
14257
|
+
const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
|
|
13528
14258
|
const add = (edgeType, kind, name) => {
|
|
13529
14259
|
if (!name) return;
|
|
13530
14260
|
const result = emitPlatformResourceEdge(
|
|
@@ -13552,16 +14282,16 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
13552
14282
|
}
|
|
13553
14283
|
|
|
13554
14284
|
// src/extract/infra/railway.ts
|
|
13555
|
-
import { promises as
|
|
13556
|
-
import
|
|
14285
|
+
import { promises as fs30 } from "fs";
|
|
14286
|
+
import path61 from "path";
|
|
13557
14287
|
import { parse as parseToml4 } from "smol-toml";
|
|
13558
14288
|
import { EdgeType as EdgeType22 } from "@neat.is/types";
|
|
13559
14289
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
13560
14290
|
async function readRailwayConfig(dir) {
|
|
13561
14291
|
for (const filename of RAILWAY_FILENAMES) {
|
|
13562
|
-
const abs =
|
|
14292
|
+
const abs = path61.join(dir, filename);
|
|
13563
14293
|
if (!await exists(abs)) continue;
|
|
13564
|
-
const raw = await
|
|
14294
|
+
const raw = await fs30.readFile(abs, "utf8");
|
|
13565
14295
|
const config = filename === "railway.toml" ? parseToml4(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
13566
14296
|
return { config, relFile: filename, raw };
|
|
13567
14297
|
}
|
|
@@ -13575,7 +14305,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
13575
14305
|
try {
|
|
13576
14306
|
read = await readRailwayConfig(service.dir);
|
|
13577
14307
|
} catch (err) {
|
|
13578
|
-
recordExtractionError("infra railway",
|
|
14308
|
+
recordExtractionError("infra railway", path61.relative(scanPath, service.dir), err);
|
|
13579
14309
|
continue;
|
|
13580
14310
|
}
|
|
13581
14311
|
if (!read) continue;
|
|
@@ -13585,7 +14315,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
13585
14315
|
}
|
|
13586
14316
|
const anchorId = service.node.id;
|
|
13587
14317
|
const { config, relFile, raw } = read;
|
|
13588
|
-
const evidenceFile = toPosix(
|
|
14318
|
+
const evidenceFile = toPosix(path61.relative(scanPath, path61.join(service.dir, relFile)));
|
|
13589
14319
|
const add = (edgeType, kind, name) => {
|
|
13590
14320
|
if (!name) return;
|
|
13591
14321
|
const result = emitPlatformResourceEdge(
|
|
@@ -13609,15 +14339,15 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
13609
14339
|
}
|
|
13610
14340
|
|
|
13611
14341
|
// src/extract/infra/supabase.ts
|
|
13612
|
-
import { promises as
|
|
13613
|
-
import
|
|
14342
|
+
import { promises as fs31 } from "fs";
|
|
14343
|
+
import path62 from "path";
|
|
13614
14344
|
import { parse as parseToml5 } from "smol-toml";
|
|
13615
14345
|
import { EdgeType as EdgeType23 } from "@neat.is/types";
|
|
13616
14346
|
async function readSupabaseConfig(dir) {
|
|
13617
|
-
const relFile =
|
|
13618
|
-
const abs =
|
|
14347
|
+
const relFile = path62.join("supabase", "config.toml");
|
|
14348
|
+
const abs = path62.join(dir, relFile);
|
|
13619
14349
|
if (!await exists(abs)) return null;
|
|
13620
|
-
const raw = await
|
|
14350
|
+
const raw = await fs31.readFile(abs, "utf8");
|
|
13621
14351
|
const config = parseToml5(raw);
|
|
13622
14352
|
return { config, relFile, raw };
|
|
13623
14353
|
}
|
|
@@ -13629,7 +14359,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
13629
14359
|
try {
|
|
13630
14360
|
read = await readSupabaseConfig(service.dir);
|
|
13631
14361
|
} catch (err) {
|
|
13632
|
-
recordExtractionError("infra supabase",
|
|
14362
|
+
recordExtractionError("infra supabase", path62.relative(scanPath, service.dir), err);
|
|
13633
14363
|
continue;
|
|
13634
14364
|
}
|
|
13635
14365
|
if (!read) continue;
|
|
@@ -13644,7 +14374,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
13644
14374
|
});
|
|
13645
14375
|
}
|
|
13646
14376
|
const anchorId = service.node.id;
|
|
13647
|
-
const evidenceFile = toPosix(
|
|
14377
|
+
const evidenceFile = toPosix(path62.relative(scanPath, path62.join(service.dir, relFile)));
|
|
13648
14378
|
const add = (edgeType, kind, name) => {
|
|
13649
14379
|
if (!name) return;
|
|
13650
14380
|
const result = emitPlatformResourceEdge(
|
|
@@ -13685,8 +14415,8 @@ async function addInfra(graph, scanPath, services) {
|
|
|
13685
14415
|
}
|
|
13686
14416
|
|
|
13687
14417
|
// src/extract/zod-shapes.ts
|
|
13688
|
-
import
|
|
13689
|
-
import
|
|
14418
|
+
import path63 from "path";
|
|
14419
|
+
import Parser17 from "tree-sitter";
|
|
13690
14420
|
import JavaScript8 from "tree-sitter-javascript";
|
|
13691
14421
|
import {
|
|
13692
14422
|
EdgeType as EdgeType24,
|
|
@@ -13694,12 +14424,12 @@ import {
|
|
|
13694
14424
|
Provenance as Provenance22,
|
|
13695
14425
|
confidenceForExtracted as confidenceForExtracted19,
|
|
13696
14426
|
extractedEdgeId as extractedEdgeId11,
|
|
13697
|
-
infraId as
|
|
14427
|
+
infraId as infraId20
|
|
13698
14428
|
} from "@neat.is/types";
|
|
13699
14429
|
var ZOD_IMPORT_RE = /\bzod\b/;
|
|
13700
14430
|
var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
|
|
13701
14431
|
function parserForExt4(ext) {
|
|
13702
|
-
const p = new
|
|
14432
|
+
const p = new Parser17();
|
|
13703
14433
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript8);
|
|
13704
14434
|
return p;
|
|
13705
14435
|
}
|
|
@@ -13787,7 +14517,7 @@ function topLevelSchemas(root) {
|
|
|
13787
14517
|
}
|
|
13788
14518
|
function zodShapesFromFile(file, serviceDir) {
|
|
13789
14519
|
if (!ZOD_IMPORT_RE.test(file.content)) return [];
|
|
13790
|
-
const tree = parseSource3(parserForExt4(
|
|
14520
|
+
const tree = parseSource3(parserForExt4(path63.extname(file.path)), file.content);
|
|
13791
14521
|
const out = [];
|
|
13792
14522
|
const seen = /* @__PURE__ */ new Set();
|
|
13793
14523
|
for (const { name, call } of topLevelSchemas(tree.rootNode)) {
|
|
@@ -13801,11 +14531,11 @@ function zodShapesFromFile(file, serviceDir) {
|
|
|
13801
14531
|
seen.add(name);
|
|
13802
14532
|
const line = call.startPosition.row + 1;
|
|
13803
14533
|
out.push({
|
|
13804
|
-
infraId:
|
|
14534
|
+
infraId: infraId20("zod-schema", name),
|
|
13805
14535
|
name,
|
|
13806
14536
|
fields,
|
|
13807
14537
|
evidence: {
|
|
13808
|
-
file:
|
|
14538
|
+
file: path63.relative(serviceDir, file.path),
|
|
13809
14539
|
line,
|
|
13810
14540
|
snippet: snippet(file.content, line)
|
|
13811
14541
|
}
|
|
@@ -14041,11 +14771,11 @@ async function addFirestoreRules(graph, services) {
|
|
|
14041
14771
|
}
|
|
14042
14772
|
|
|
14043
14773
|
// src/extract/index.ts
|
|
14044
|
-
import
|
|
14774
|
+
import path65 from "path";
|
|
14045
14775
|
|
|
14046
14776
|
// src/extract/retire.ts
|
|
14047
14777
|
import { existsSync as existsSync2 } from "fs";
|
|
14048
|
-
import
|
|
14778
|
+
import path64 from "path";
|
|
14049
14779
|
import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
|
|
14050
14780
|
function dropOrphanedFileNodes(graph) {
|
|
14051
14781
|
const orphans = [];
|
|
@@ -14079,11 +14809,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
14079
14809
|
if (edge.provenance !== Provenance23.EXTRACTED) return;
|
|
14080
14810
|
const evidenceFile = edge.evidence?.file;
|
|
14081
14811
|
if (!evidenceFile) return;
|
|
14082
|
-
if (
|
|
14812
|
+
if (path64.isAbsolute(evidenceFile)) {
|
|
14083
14813
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
14084
14814
|
return;
|
|
14085
14815
|
}
|
|
14086
|
-
const found = bases.some((base) => existsSync2(
|
|
14816
|
+
const found = bases.some((base) => existsSync2(path64.join(base, evidenceFile)));
|
|
14087
14817
|
if (!found) toDrop.push(id);
|
|
14088
14818
|
});
|
|
14089
14819
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -14140,7 +14870,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
14140
14870
|
}
|
|
14141
14871
|
const droppedEntries = drainDroppedExtracted();
|
|
14142
14872
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
14143
|
-
const rejectedPath =
|
|
14873
|
+
const rejectedPath = path65.join(path65.dirname(opts.errorsPath), "rejected.ndjson");
|
|
14144
14874
|
try {
|
|
14145
14875
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
14146
14876
|
} catch (err) {
|
|
@@ -14514,8 +15244,8 @@ function computeDivergences(graph, opts = {}) {
|
|
|
14514
15244
|
}
|
|
14515
15245
|
|
|
14516
15246
|
// src/persist.ts
|
|
14517
|
-
import { promises as
|
|
14518
|
-
import
|
|
15247
|
+
import { promises as fs32 } from "fs";
|
|
15248
|
+
import path66 from "path";
|
|
14519
15249
|
import { NodeType as NodeType31, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
14520
15250
|
var SCHEMA_VERSION = 7;
|
|
14521
15251
|
function migrateV1ToV2(payload) {
|
|
@@ -14570,7 +15300,7 @@ function migrateV2ToV3(payload) {
|
|
|
14570
15300
|
return { ...payload, schemaVersion: 3 };
|
|
14571
15301
|
}
|
|
14572
15302
|
async function ensureDir(filePath) {
|
|
14573
|
-
await
|
|
15303
|
+
await fs32.mkdir(path66.dirname(filePath), { recursive: true });
|
|
14574
15304
|
}
|
|
14575
15305
|
async function saveGraphToDisk(graph, outPath) {
|
|
14576
15306
|
await ensureDir(outPath);
|
|
@@ -14580,13 +15310,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
14580
15310
|
graph: graph.export()
|
|
14581
15311
|
};
|
|
14582
15312
|
const tmp = `${outPath}.tmp`;
|
|
14583
|
-
await
|
|
14584
|
-
await
|
|
15313
|
+
await fs32.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
15314
|
+
await fs32.rename(tmp, outPath);
|
|
14585
15315
|
}
|
|
14586
15316
|
async function loadGraphFromDisk(graph, outPath) {
|
|
14587
15317
|
let raw;
|
|
14588
15318
|
try {
|
|
14589
|
-
raw = await
|
|
15319
|
+
raw = await fs32.readFile(outPath, "utf8");
|
|
14590
15320
|
} catch (err) {
|
|
14591
15321
|
if (err.code === "ENOENT") return;
|
|
14592
15322
|
throw err;
|
|
@@ -14659,7 +15389,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
14659
15389
|
}
|
|
14660
15390
|
|
|
14661
15391
|
// src/diff.ts
|
|
14662
|
-
import { promises as
|
|
15392
|
+
import { promises as fs33 } from "fs";
|
|
14663
15393
|
async function loadSnapshotForDiff(target) {
|
|
14664
15394
|
if (/^https?:\/\//i.test(target)) {
|
|
14665
15395
|
const res = await fetch(target);
|
|
@@ -14668,7 +15398,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
14668
15398
|
}
|
|
14669
15399
|
return await res.json();
|
|
14670
15400
|
}
|
|
14671
|
-
const raw = await
|
|
15401
|
+
const raw = await fs33.readFile(target, "utf8");
|
|
14672
15402
|
return JSON.parse(raw);
|
|
14673
15403
|
}
|
|
14674
15404
|
function indexEntries(entries) {
|
|
@@ -14735,23 +15465,23 @@ function canonicalJson(value) {
|
|
|
14735
15465
|
}
|
|
14736
15466
|
|
|
14737
15467
|
// src/projects.ts
|
|
14738
|
-
import
|
|
15468
|
+
import path67 from "path";
|
|
14739
15469
|
function pathsForProject(project, baseDir) {
|
|
14740
15470
|
if (project === DEFAULT_PROJECT) {
|
|
14741
15471
|
return {
|
|
14742
|
-
snapshotPath:
|
|
14743
|
-
errorsPath:
|
|
14744
|
-
staleEventsPath:
|
|
14745
|
-
embeddingsCachePath:
|
|
14746
|
-
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")
|
|
14747
15477
|
};
|
|
14748
15478
|
}
|
|
14749
15479
|
return {
|
|
14750
|
-
snapshotPath:
|
|
14751
|
-
errorsPath:
|
|
14752
|
-
staleEventsPath:
|
|
14753
|
-
embeddingsCachePath:
|
|
14754
|
-
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`)
|
|
14755
15485
|
};
|
|
14756
15486
|
}
|
|
14757
15487
|
var Projects = class {
|
|
@@ -14790,9 +15520,9 @@ function parseExtraProjects(raw) {
|
|
|
14790
15520
|
}
|
|
14791
15521
|
|
|
14792
15522
|
// src/registry.ts
|
|
14793
|
-
import { promises as
|
|
15523
|
+
import { promises as fs34 } from "fs";
|
|
14794
15524
|
import os2 from "os";
|
|
14795
|
-
import
|
|
15525
|
+
import path68 from "path";
|
|
14796
15526
|
import {
|
|
14797
15527
|
RegistryFileSchema
|
|
14798
15528
|
} from "@neat.is/types";
|
|
@@ -14800,20 +15530,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
14800
15530
|
var LOCK_RETRY_MS = 50;
|
|
14801
15531
|
function neatHome() {
|
|
14802
15532
|
const override = process.env.NEAT_HOME;
|
|
14803
|
-
if (override && override.length > 0) return
|
|
14804
|
-
return
|
|
15533
|
+
if (override && override.length > 0) return path68.resolve(override);
|
|
15534
|
+
return path68.join(os2.homedir(), ".neat");
|
|
14805
15535
|
}
|
|
14806
15536
|
function registryPath() {
|
|
14807
|
-
return
|
|
15537
|
+
return path68.join(neatHome(), "projects.json");
|
|
14808
15538
|
}
|
|
14809
15539
|
function registryLockPath() {
|
|
14810
|
-
return
|
|
15540
|
+
return path68.join(neatHome(), "projects.json.lock");
|
|
14811
15541
|
}
|
|
14812
15542
|
function daemonPidPath() {
|
|
14813
|
-
return
|
|
15543
|
+
return path68.join(neatHome(), "neatd.pid");
|
|
14814
15544
|
}
|
|
14815
15545
|
function daemonsDir() {
|
|
14816
|
-
return
|
|
15546
|
+
return path68.join(neatHome(), "daemons");
|
|
14817
15547
|
}
|
|
14818
15548
|
function isFiniteInt(v) {
|
|
14819
15549
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -14846,7 +15576,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
14846
15576
|
const dir = daemonsDir();
|
|
14847
15577
|
let names;
|
|
14848
15578
|
try {
|
|
14849
|
-
names = await
|
|
15579
|
+
names = await fs34.readdir(dir);
|
|
14850
15580
|
} catch (err) {
|
|
14851
15581
|
if (err.code === "ENOENT") return [];
|
|
14852
15582
|
throw err;
|
|
@@ -14854,10 +15584,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
14854
15584
|
const out = [];
|
|
14855
15585
|
for (const name of names) {
|
|
14856
15586
|
if (!name.endsWith(".json")) continue;
|
|
14857
|
-
const file =
|
|
15587
|
+
const file = path68.join(dir, name);
|
|
14858
15588
|
let raw;
|
|
14859
15589
|
try {
|
|
14860
|
-
raw = await
|
|
15590
|
+
raw = await fs34.readFile(file, "utf8");
|
|
14861
15591
|
} catch {
|
|
14862
15592
|
continue;
|
|
14863
15593
|
}
|
|
@@ -14870,7 +15600,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
14870
15600
|
return out;
|
|
14871
15601
|
}
|
|
14872
15602
|
async function removeDaemonRecord(source) {
|
|
14873
|
-
await
|
|
15603
|
+
await fs34.unlink(source).catch(() => {
|
|
14874
15604
|
});
|
|
14875
15605
|
}
|
|
14876
15606
|
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
@@ -14927,7 +15657,7 @@ function isPidAliveDefault(pid) {
|
|
|
14927
15657
|
}
|
|
14928
15658
|
async function readPidFile(file) {
|
|
14929
15659
|
try {
|
|
14930
|
-
const raw = await
|
|
15660
|
+
const raw = await fs34.readFile(file, "utf8");
|
|
14931
15661
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
14932
15662
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
14933
15663
|
} catch {
|
|
@@ -14975,32 +15705,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
14975
15705
|
}
|
|
14976
15706
|
}
|
|
14977
15707
|
async function normalizeProjectPath(input) {
|
|
14978
|
-
const resolved =
|
|
15708
|
+
const resolved = path68.resolve(input);
|
|
14979
15709
|
try {
|
|
14980
|
-
return await
|
|
15710
|
+
return await fs34.realpath(resolved);
|
|
14981
15711
|
} catch {
|
|
14982
15712
|
return resolved;
|
|
14983
15713
|
}
|
|
14984
15714
|
}
|
|
14985
15715
|
async function writeAtomically(target, contents) {
|
|
14986
|
-
await
|
|
15716
|
+
await fs34.mkdir(path68.dirname(target), { recursive: true });
|
|
14987
15717
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
14988
|
-
const fd = await
|
|
15718
|
+
const fd = await fs34.open(tmp, "w");
|
|
14989
15719
|
try {
|
|
14990
15720
|
await fd.writeFile(contents, "utf8");
|
|
14991
15721
|
await fd.sync();
|
|
14992
15722
|
} finally {
|
|
14993
15723
|
await fd.close();
|
|
14994
15724
|
}
|
|
14995
|
-
await
|
|
15725
|
+
await fs34.rename(tmp, target);
|
|
14996
15726
|
}
|
|
14997
15727
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
14998
15728
|
const deadline = Date.now() + timeoutMs;
|
|
14999
|
-
await
|
|
15729
|
+
await fs34.mkdir(path68.dirname(lockPath), { recursive: true });
|
|
15000
15730
|
let probedHolder = false;
|
|
15001
15731
|
while (true) {
|
|
15002
15732
|
try {
|
|
15003
|
-
const fd = await
|
|
15733
|
+
const fd = await fs34.open(lockPath, "wx");
|
|
15004
15734
|
try {
|
|
15005
15735
|
await fd.writeFile(`${process.pid}
|
|
15006
15736
|
`, "utf8");
|
|
@@ -15025,7 +15755,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
15025
15755
|
}
|
|
15026
15756
|
}
|
|
15027
15757
|
async function releaseLock(lockPath) {
|
|
15028
|
-
await
|
|
15758
|
+
await fs34.unlink(lockPath).catch(() => {
|
|
15029
15759
|
});
|
|
15030
15760
|
}
|
|
15031
15761
|
async function withLock(fn) {
|
|
@@ -15041,7 +15771,7 @@ async function readRegistry() {
|
|
|
15041
15771
|
const file = registryPath();
|
|
15042
15772
|
let raw;
|
|
15043
15773
|
try {
|
|
15044
|
-
raw = await
|
|
15774
|
+
raw = await fs34.readFile(file, "utf8");
|
|
15045
15775
|
} catch (err) {
|
|
15046
15776
|
if (err.code === "ENOENT") {
|
|
15047
15777
|
return { version: 1, projects: [] };
|
|
@@ -15146,7 +15876,7 @@ function pruneTtlMs() {
|
|
|
15146
15876
|
}
|
|
15147
15877
|
async function statPathStatus(p) {
|
|
15148
15878
|
try {
|
|
15149
|
-
const stat = await
|
|
15879
|
+
const stat = await fs34.stat(p);
|
|
15150
15880
|
return stat.isDirectory() ? "present" : "unknown";
|
|
15151
15881
|
} catch (err) {
|
|
15152
15882
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -15191,14 +15921,14 @@ import cors from "@fastify/cors";
|
|
|
15191
15921
|
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
15192
15922
|
|
|
15193
15923
|
// src/extend/index.ts
|
|
15194
|
-
import { promises as
|
|
15195
|
-
import
|
|
15924
|
+
import { promises as fs36 } from "fs";
|
|
15925
|
+
import path70 from "path";
|
|
15196
15926
|
import os3 from "os";
|
|
15197
15927
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
15198
15928
|
|
|
15199
15929
|
// src/installers/package-manager.ts
|
|
15200
|
-
import { promises as
|
|
15201
|
-
import
|
|
15930
|
+
import { promises as fs35 } from "fs";
|
|
15931
|
+
import path69 from "path";
|
|
15202
15932
|
import { spawn } from "child_process";
|
|
15203
15933
|
var LOCKFILE_PRIORITY = [
|
|
15204
15934
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -15213,29 +15943,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
15213
15943
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
15214
15944
|
async function exists2(p) {
|
|
15215
15945
|
try {
|
|
15216
|
-
await
|
|
15946
|
+
await fs35.access(p);
|
|
15217
15947
|
return true;
|
|
15218
15948
|
} catch {
|
|
15219
15949
|
return false;
|
|
15220
15950
|
}
|
|
15221
15951
|
}
|
|
15222
15952
|
async function detectPackageManager(serviceDir) {
|
|
15223
|
-
let dir =
|
|
15953
|
+
let dir = path69.resolve(serviceDir);
|
|
15224
15954
|
const stops = /* @__PURE__ */ new Set();
|
|
15225
15955
|
for (let i = 0; i < 64; i++) {
|
|
15226
15956
|
if (stops.has(dir)) break;
|
|
15227
15957
|
stops.add(dir);
|
|
15228
15958
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
15229
|
-
const lockPath =
|
|
15959
|
+
const lockPath = path69.join(dir, candidate.lockfile);
|
|
15230
15960
|
if (await exists2(lockPath)) {
|
|
15231
15961
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
15232
15962
|
}
|
|
15233
15963
|
}
|
|
15234
|
-
const parent =
|
|
15964
|
+
const parent = path69.dirname(dir);
|
|
15235
15965
|
if (parent === dir) break;
|
|
15236
15966
|
dir = parent;
|
|
15237
15967
|
}
|
|
15238
|
-
return { pm: "npm", cwd:
|
|
15968
|
+
return { pm: "npm", cwd: path69.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
15239
15969
|
}
|
|
15240
15970
|
async function runPackageManagerInstall(cmd) {
|
|
15241
15971
|
return new Promise((resolve) => {
|
|
@@ -15277,15 +16007,15 @@ ${err.message}`
|
|
|
15277
16007
|
// src/extend/index.ts
|
|
15278
16008
|
async function fileExists2(p) {
|
|
15279
16009
|
try {
|
|
15280
|
-
await
|
|
16010
|
+
await fs36.access(p);
|
|
15281
16011
|
return true;
|
|
15282
16012
|
} catch {
|
|
15283
16013
|
return false;
|
|
15284
16014
|
}
|
|
15285
16015
|
}
|
|
15286
16016
|
async function readPackageJson(scanPath) {
|
|
15287
|
-
const pkgPath =
|
|
15288
|
-
const raw = await
|
|
16017
|
+
const pkgPath = path70.join(scanPath, "package.json");
|
|
16018
|
+
const raw = await fs36.readFile(pkgPath, "utf8");
|
|
15289
16019
|
return JSON.parse(raw);
|
|
15290
16020
|
}
|
|
15291
16021
|
var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -15298,27 +16028,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
15298
16028
|
]);
|
|
15299
16029
|
async function findHookFiles(scanPath) {
|
|
15300
16030
|
const found = [];
|
|
15301
|
-
const
|
|
15302
|
-
const entries = await
|
|
16031
|
+
const walk10 = async (dir) => {
|
|
16032
|
+
const entries = await fs36.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
15303
16033
|
for (const entry of entries) {
|
|
15304
16034
|
if (entry.isDirectory()) {
|
|
15305
16035
|
if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
|
|
15306
|
-
await
|
|
16036
|
+
await walk10(path70.join(dir, entry.name));
|
|
15307
16037
|
} else if (entry.isFile()) {
|
|
15308
16038
|
if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
|
|
15309
|
-
const rel =
|
|
15310
|
-
found.push(rel.split(
|
|
16039
|
+
const rel = path70.relative(scanPath, path70.join(dir, entry.name));
|
|
16040
|
+
found.push(rel.split(path70.sep).join("/"));
|
|
15311
16041
|
}
|
|
15312
16042
|
}
|
|
15313
16043
|
}
|
|
15314
16044
|
};
|
|
15315
|
-
await
|
|
16045
|
+
await walk10(scanPath);
|
|
15316
16046
|
return found.sort();
|
|
15317
16047
|
}
|
|
15318
16048
|
async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
15319
16049
|
let fallback = null;
|
|
15320
16050
|
for (const file of hookFiles) {
|
|
15321
|
-
const content = await
|
|
16051
|
+
const content = await fs36.readFile(path70.join(scanPath, file), "utf8");
|
|
15322
16052
|
const patched = splicedContent(content, snippet2);
|
|
15323
16053
|
if (patched !== null) return { file, content, patched };
|
|
15324
16054
|
if (fallback === null) fallback = { file, content };
|
|
@@ -15326,12 +16056,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
|
15326
16056
|
return { file: fallback.file, content: fallback.content, patched: null };
|
|
15327
16057
|
}
|
|
15328
16058
|
function extendLogPath() {
|
|
15329
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
16059
|
+
return process.env.NEAT_EXTEND_LOG ?? path70.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
15330
16060
|
}
|
|
15331
16061
|
async function appendExtendLog(entry) {
|
|
15332
16062
|
const logPath = extendLogPath();
|
|
15333
|
-
await
|
|
15334
|
-
await
|
|
16063
|
+
await fs36.mkdir(path70.dirname(logPath), { recursive: true });
|
|
16064
|
+
await fs36.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
15335
16065
|
}
|
|
15336
16066
|
function splicedContent(fileContent, snippet2) {
|
|
15337
16067
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -15389,7 +16119,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
15389
16119
|
}
|
|
15390
16120
|
async function describeProjectInstrumentation(ctx) {
|
|
15391
16121
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
15392
|
-
const envNeat = await fileExists2(
|
|
16122
|
+
const envNeat = await fileExists2(path70.join(ctx.scanPath, ".env.neat"));
|
|
15393
16123
|
const registryInstrPackages = new Set(
|
|
15394
16124
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
15395
16125
|
);
|
|
@@ -15411,7 +16141,7 @@ async function applyExtension(ctx, args, options) {
|
|
|
15411
16141
|
);
|
|
15412
16142
|
}
|
|
15413
16143
|
for (const file of hookFiles) {
|
|
15414
|
-
const content = await
|
|
16144
|
+
const content = await fs36.readFile(path70.join(ctx.scanPath, file), "utf8");
|
|
15415
16145
|
if (content.includes(args.registration_snippet)) {
|
|
15416
16146
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
15417
16147
|
}
|
|
@@ -15423,18 +16153,18 @@ async function applyExtension(ctx, args, options) {
|
|
|
15423
16153
|
);
|
|
15424
16154
|
}
|
|
15425
16155
|
const primaryFile = primary.file;
|
|
15426
|
-
const primaryPath =
|
|
16156
|
+
const primaryPath = path70.join(ctx.scanPath, primaryFile);
|
|
15427
16157
|
const filesTouched = [];
|
|
15428
16158
|
const depsAdded = [];
|
|
15429
|
-
const pkgPath =
|
|
16159
|
+
const pkgPath = path70.join(ctx.scanPath, "package.json");
|
|
15430
16160
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
15431
16161
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
15432
16162
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
15433
|
-
await
|
|
16163
|
+
await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
15434
16164
|
filesTouched.push("package.json");
|
|
15435
16165
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
15436
16166
|
}
|
|
15437
|
-
await
|
|
16167
|
+
await fs36.writeFile(primaryPath, primary.patched, "utf8");
|
|
15438
16168
|
filesTouched.push(primaryFile);
|
|
15439
16169
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
15440
16170
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -15465,7 +16195,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
15465
16195
|
};
|
|
15466
16196
|
}
|
|
15467
16197
|
for (const file of hookFiles) {
|
|
15468
|
-
const content = await
|
|
16198
|
+
const content = await fs36.readFile(path70.join(ctx.scanPath, file), "utf8");
|
|
15469
16199
|
if (content.includes(args.registration_snippet)) {
|
|
15470
16200
|
return {
|
|
15471
16201
|
library: args.library,
|
|
@@ -15500,28 +16230,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
15500
16230
|
if (!await fileExists2(logPath)) {
|
|
15501
16231
|
return { undone: false, message: "no apply found for library" };
|
|
15502
16232
|
}
|
|
15503
|
-
const raw = await
|
|
16233
|
+
const raw = await fs36.readFile(logPath, "utf8");
|
|
15504
16234
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
15505
16235
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
15506
16236
|
if (!match) {
|
|
15507
16237
|
return { undone: false, message: "no apply found for library" };
|
|
15508
16238
|
}
|
|
15509
|
-
const pkgPath =
|
|
16239
|
+
const pkgPath = path70.join(ctx.scanPath, "package.json");
|
|
15510
16240
|
if (await fileExists2(pkgPath)) {
|
|
15511
16241
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
15512
16242
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
15513
16243
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
15514
16244
|
pkg.dependencies = rest;
|
|
15515
|
-
await
|
|
16245
|
+
await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
15516
16246
|
}
|
|
15517
16247
|
}
|
|
15518
16248
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
15519
16249
|
for (const file of hookFiles) {
|
|
15520
|
-
const filePath =
|
|
15521
|
-
const content = await
|
|
16250
|
+
const filePath = path70.join(ctx.scanPath, file);
|
|
16251
|
+
const content = await fs36.readFile(filePath, "utf8");
|
|
15522
16252
|
if (content.includes(match.registration_snippet)) {
|
|
15523
16253
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
15524
|
-
await
|
|
16254
|
+
await fs36.writeFile(filePath, filtered, "utf8");
|
|
15525
16255
|
break;
|
|
15526
16256
|
}
|
|
15527
16257
|
}
|
|
@@ -15632,8 +16362,8 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
15632
16362
|
|
|
15633
16363
|
// src/connectors-config.ts
|
|
15634
16364
|
import os4 from "os";
|
|
15635
|
-
import
|
|
15636
|
-
import { promises as
|
|
16365
|
+
import path71 from "path";
|
|
16366
|
+
import { promises as fs37 } from "fs";
|
|
15637
16367
|
var CONNECTORS_CONFIG_VERSION = 1;
|
|
15638
16368
|
var EnvRefUnsetError = class extends Error {
|
|
15639
16369
|
ref;
|
|
@@ -15647,17 +16377,17 @@ var EnvRefUnsetError = class extends Error {
|
|
|
15647
16377
|
};
|
|
15648
16378
|
function neatHome2() {
|
|
15649
16379
|
const override = process.env.NEAT_HOME;
|
|
15650
|
-
if (override && override.length > 0) return
|
|
15651
|
-
return
|
|
16380
|
+
if (override && override.length > 0) return path71.resolve(override);
|
|
16381
|
+
return path71.join(os4.homedir(), ".neat");
|
|
15652
16382
|
}
|
|
15653
16383
|
function connectorsConfigPath(home = neatHome2()) {
|
|
15654
|
-
return
|
|
16384
|
+
return path71.join(home, "connectors.json");
|
|
15655
16385
|
}
|
|
15656
16386
|
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
15657
16387
|
async function warnIfModeLooserThan0600(file) {
|
|
15658
16388
|
if (process.platform === "win32") return;
|
|
15659
16389
|
try {
|
|
15660
|
-
const stat = await
|
|
16390
|
+
const stat = await fs37.stat(file);
|
|
15661
16391
|
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
15662
16392
|
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
15663
16393
|
console.warn(
|
|
@@ -15671,7 +16401,7 @@ async function readConnectorsConfig(home = neatHome2()) {
|
|
|
15671
16401
|
const file = connectorsConfigPath(home);
|
|
15672
16402
|
let raw;
|
|
15673
16403
|
try {
|
|
15674
|
-
raw = await
|
|
16404
|
+
raw = await fs37.readFile(file, "utf8");
|
|
15675
16405
|
} catch (err) {
|
|
15676
16406
|
if (err.code === "ENOENT") {
|
|
15677
16407
|
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
@@ -15782,7 +16512,7 @@ function connectorMatchesProject(entry, project) {
|
|
|
15782
16512
|
var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
|
|
15783
16513
|
var CONNECTORS_LOCK_RETRY_MS = 50;
|
|
15784
16514
|
function connectorsConfigLockPath(home = neatHome2()) {
|
|
15785
|
-
return
|
|
16515
|
+
return path71.join(home, "connectors.json.lock");
|
|
15786
16516
|
}
|
|
15787
16517
|
function isEnvRef(value) {
|
|
15788
16518
|
return value.length > 1 && value.startsWith("$");
|
|
@@ -15795,9 +16525,9 @@ function redactCredentialRef(ref) {
|
|
|
15795
16525
|
return out;
|
|
15796
16526
|
}
|
|
15797
16527
|
async function writeConfigAtomically0600(file, contents) {
|
|
15798
|
-
await
|
|
16528
|
+
await fs37.mkdir(path71.dirname(file), { recursive: true });
|
|
15799
16529
|
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
15800
|
-
const fd = await
|
|
16530
|
+
const fd = await fs37.open(tmp, "w", 384);
|
|
15801
16531
|
try {
|
|
15802
16532
|
await fd.writeFile(contents, "utf8");
|
|
15803
16533
|
await fd.chmod(384);
|
|
@@ -15805,14 +16535,14 @@ async function writeConfigAtomically0600(file, contents) {
|
|
|
15805
16535
|
} finally {
|
|
15806
16536
|
await fd.close();
|
|
15807
16537
|
}
|
|
15808
|
-
await
|
|
16538
|
+
await fs37.rename(tmp, file);
|
|
15809
16539
|
}
|
|
15810
16540
|
async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
|
|
15811
16541
|
const deadline = Date.now() + timeoutMs;
|
|
15812
|
-
await
|
|
16542
|
+
await fs37.mkdir(path71.dirname(lockPath), { recursive: true });
|
|
15813
16543
|
for (; ; ) {
|
|
15814
16544
|
try {
|
|
15815
|
-
const fd = await
|
|
16545
|
+
const fd = await fs37.open(lockPath, "wx");
|
|
15816
16546
|
try {
|
|
15817
16547
|
await fd.writeFile(`${process.pid}
|
|
15818
16548
|
`, "utf8");
|
|
@@ -15832,7 +16562,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
|
|
|
15832
16562
|
}
|
|
15833
16563
|
}
|
|
15834
16564
|
async function releaseConnectorsLock(lockPath) {
|
|
15835
|
-
await
|
|
16565
|
+
await fs37.unlink(lockPath).catch(() => {
|
|
15836
16566
|
});
|
|
15837
16567
|
}
|
|
15838
16568
|
async function withConnectorsLock(home, fn) {
|
|
@@ -16461,10 +17191,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
16461
17191
|
// src/connectors/supabase/map.ts
|
|
16462
17192
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
16463
17193
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
16464
|
-
function targetFromRestPath(
|
|
16465
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
17194
|
+
function targetFromRestPath(path72) {
|
|
17195
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path72);
|
|
16466
17196
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
16467
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
17197
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path72);
|
|
16468
17198
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
16469
17199
|
return null;
|
|
16470
17200
|
}
|
|
@@ -16573,21 +17303,21 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
16573
17303
|
}
|
|
16574
17304
|
|
|
16575
17305
|
// src/connectors/supabase/resolve.ts
|
|
16576
|
-
import { EdgeType as EdgeType26, infraId as
|
|
17306
|
+
import { EdgeType as EdgeType26, infraId as infraId21 } from "@neat.is/types";
|
|
16577
17307
|
function createSupabaseResolveTarget(graph, config) {
|
|
16578
17308
|
return (signal, _ctx) => {
|
|
16579
17309
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
16580
17310
|
return null;
|
|
16581
17311
|
}
|
|
16582
|
-
const subResourceId =
|
|
17312
|
+
const subResourceId = infraId21(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
16583
17313
|
if (graph.hasNode(subResourceId)) {
|
|
16584
17314
|
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
|
|
16585
17315
|
}
|
|
16586
|
-
const bareResourceId =
|
|
17316
|
+
const bareResourceId = infraId21(signal.targetKind, signal.targetName);
|
|
16587
17317
|
if (graph.hasNode(bareResourceId)) {
|
|
16588
17318
|
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
|
|
16589
17319
|
}
|
|
16590
|
-
const projectLevelId =
|
|
17320
|
+
const projectLevelId = infraId21("supabase", config.nodeRef);
|
|
16591
17321
|
if (graph.hasNode(projectLevelId)) {
|
|
16592
17322
|
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
|
|
16593
17323
|
}
|
|
@@ -17059,9 +17789,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
17059
17789
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
17060
17790
|
if (secondSep === -1) return null;
|
|
17061
17791
|
const method = rest.slice(0, secondSep);
|
|
17062
|
-
const
|
|
17063
|
-
if (!resourceName || !method || !
|
|
17064
|
-
return { resourceName, method, path:
|
|
17792
|
+
const path72 = rest.slice(secondSep + 1);
|
|
17793
|
+
if (!resourceName || !method || !path72) return null;
|
|
17794
|
+
return { resourceName, method, path: path72 };
|
|
17065
17795
|
}
|
|
17066
17796
|
function resourceNameFor(type, labels) {
|
|
17067
17797
|
if (!labels) return null;
|
|
@@ -17099,14 +17829,14 @@ function mapLogEntryToSignal(entry) {
|
|
|
17099
17829
|
if (!req) return null;
|
|
17100
17830
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
17101
17831
|
const method = req.requestMethod.toUpperCase();
|
|
17102
|
-
const
|
|
17103
|
-
if (
|
|
17832
|
+
const path72 = pathFromRequestUrl(req.requestUrl);
|
|
17833
|
+
if (path72 === null) return null;
|
|
17104
17834
|
const timestamp = entry.timestamp;
|
|
17105
17835
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
17106
17836
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
17107
17837
|
return {
|
|
17108
17838
|
targetKind: resourceType,
|
|
17109
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
17839
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path72 }),
|
|
17110
17840
|
callCount: 1,
|
|
17111
17841
|
errorCount: isError ? 1 : 0,
|
|
17112
17842
|
lastObservedIso: timestamp
|
|
@@ -17192,7 +17922,7 @@ function createFirebaseConnector(graph, serviceMap) {
|
|
|
17192
17922
|
}
|
|
17193
17923
|
|
|
17194
17924
|
// src/connectors/cloudflare/connector.ts
|
|
17195
|
-
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";
|
|
17196
17926
|
|
|
17197
17927
|
// src/connectors/cloudflare/client.ts
|
|
17198
17928
|
import { randomUUID } from "crypto";
|
|
@@ -17303,7 +18033,7 @@ function mapEventToSignal(event) {
|
|
|
17303
18033
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
17304
18034
|
const statusCode = metadata?.statusCode;
|
|
17305
18035
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
17306
|
-
const
|
|
18036
|
+
const path72 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
17307
18037
|
return {
|
|
17308
18038
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
17309
18039
|
targetName: scriptName,
|
|
@@ -17311,7 +18041,7 @@ function mapEventToSignal(event) {
|
|
|
17311
18041
|
errorCount: isError ? 1 : 0,
|
|
17312
18042
|
lastObservedIso: observedAt.toISOString(),
|
|
17313
18043
|
method,
|
|
17314
|
-
...
|
|
18044
|
+
...path72 ? { path: path72 } : {},
|
|
17315
18045
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
17316
18046
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
17317
18047
|
};
|
|
@@ -17357,8 +18087,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
17357
18087
|
});
|
|
17358
18088
|
return found;
|
|
17359
18089
|
}
|
|
17360
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
17361
|
-
const normalizedPath = normalizePathTemplate(
|
|
18090
|
+
function findMatchingRouteNode(graph, serviceName, method, path72) {
|
|
18091
|
+
const normalizedPath = normalizePathTemplate(path72);
|
|
17362
18092
|
let found = null;
|
|
17363
18093
|
graph.forEachNode((id, attrs) => {
|
|
17364
18094
|
if (found) return;
|
|
@@ -17375,10 +18105,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
17375
18105
|
return (signal) => {
|
|
17376
18106
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
17377
18107
|
const scriptName = signal.targetName;
|
|
17378
|
-
const { method, path:
|
|
18108
|
+
const { method, path: path72 } = signal;
|
|
17379
18109
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
17380
|
-
if (!method || !
|
|
17381
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
18110
|
+
if (!method || !path72) return wholeFileId;
|
|
18111
|
+
return findMatchingRouteNode(graph, serviceName, method, path72) ?? wholeFileId;
|
|
17382
18112
|
};
|
|
17383
18113
|
const mapping = config.workers?.[scriptName];
|
|
17384
18114
|
if (mapping) {
|
|
@@ -17399,7 +18129,7 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
17399
18129
|
};
|
|
17400
18130
|
}
|
|
17401
18131
|
return {
|
|
17402
|
-
targetNodeId:
|
|
18132
|
+
targetNodeId: infraId22("cloudflare-worker", scriptName),
|
|
17403
18133
|
serviceName: scriptName,
|
|
17404
18134
|
edgeType: EdgeType29.CALLS,
|
|
17405
18135
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
@@ -17583,12 +18313,12 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
17583
18313
|
}
|
|
17584
18314
|
|
|
17585
18315
|
// src/connectors/neon/resolve.ts
|
|
17586
|
-
import { EdgeType as EdgeType30, infraId as
|
|
18316
|
+
import { EdgeType as EdgeType30, infraId as infraId23 } from "@neat.is/types";
|
|
17587
18317
|
function createNeonResolveTarget(config) {
|
|
17588
18318
|
return (signal) => {
|
|
17589
18319
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
17590
18320
|
return {
|
|
17591
|
-
targetNodeId:
|
|
18321
|
+
targetNodeId: infraId23("sql-table", signal.targetName),
|
|
17592
18322
|
serviceName: config.serviceName,
|
|
17593
18323
|
edgeType: EdgeType30.CALLS,
|
|
17594
18324
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
@@ -17708,9 +18438,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
17708
18438
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
17709
18439
|
if (secondSep === -1) return null;
|
|
17710
18440
|
const method = rest.slice(0, secondSep);
|
|
17711
|
-
const
|
|
17712
|
-
if (!serviceName || !method || !
|
|
17713
|
-
return { serviceName, method, path:
|
|
18441
|
+
const path72 = rest.slice(secondSep + 1);
|
|
18442
|
+
if (!serviceName || !method || !path72) return null;
|
|
18443
|
+
return { serviceName, method, path: path72 };
|
|
17714
18444
|
}
|
|
17715
18445
|
|
|
17716
18446
|
// src/connectors/cloud-run/map.ts
|
|
@@ -17739,14 +18469,14 @@ function mapLogEntryToSignal2(entry) {
|
|
|
17739
18469
|
if (!req) return null;
|
|
17740
18470
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
17741
18471
|
const method = req.requestMethod.toUpperCase();
|
|
17742
|
-
const
|
|
17743
|
-
if (
|
|
18472
|
+
const path72 = pathFromRequestUrl2(req.requestUrl);
|
|
18473
|
+
if (path72 === null) return null;
|
|
17744
18474
|
const timestamp = entry.timestamp;
|
|
17745
18475
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
17746
18476
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
17747
18477
|
return {
|
|
17748
18478
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
17749
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
18479
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path72 }),
|
|
17750
18480
|
callCount: 1,
|
|
17751
18481
|
errorCount: isError ? 1 : 0,
|
|
17752
18482
|
lastObservedIso: timestamp
|
|
@@ -17762,7 +18492,7 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
17762
18492
|
}
|
|
17763
18493
|
|
|
17764
18494
|
// src/connectors/cloud-run/resolve.ts
|
|
17765
|
-
import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as
|
|
18495
|
+
import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as infraId24 } from "@neat.is/types";
|
|
17766
18496
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
17767
18497
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
17768
18498
|
let found = null;
|
|
@@ -17784,21 +18514,21 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
17784
18514
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
17785
18515
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
17786
18516
|
if (!identity) return null;
|
|
17787
|
-
const { serviceName: gcpServiceName, method, path:
|
|
18517
|
+
const { serviceName: gcpServiceName, method, path: path72 } = identity;
|
|
17788
18518
|
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
17789
18519
|
if (mappedService) {
|
|
17790
18520
|
const routeNodeId = findMatchingRouteNode2(
|
|
17791
18521
|
graph,
|
|
17792
18522
|
mappedService,
|
|
17793
18523
|
method,
|
|
17794
|
-
normalizePathTemplate(
|
|
18524
|
+
normalizePathTemplate(path72)
|
|
17795
18525
|
);
|
|
17796
18526
|
if (routeNodeId) {
|
|
17797
18527
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
|
|
17798
18528
|
}
|
|
17799
18529
|
}
|
|
17800
18530
|
return {
|
|
17801
|
-
targetNodeId:
|
|
18531
|
+
targetNodeId: infraId24(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
17802
18532
|
serviceName: mappedService ?? gcpServiceName,
|
|
17803
18533
|
edgeType: EdgeType31.CALLS,
|
|
17804
18534
|
ensureInfraNode: {
|
|
@@ -18130,17 +18860,17 @@ function mapInsightsToSignals(rows, observedAtIso) {
|
|
|
18130
18860
|
}
|
|
18131
18861
|
|
|
18132
18862
|
// src/connectors/planetscale/resolve.ts
|
|
18133
|
-
import { EdgeType as EdgeType33, infraId as
|
|
18863
|
+
import { EdgeType as EdgeType33, infraId as infraId25 } from "@neat.is/types";
|
|
18134
18864
|
var PLANETSCALE_DATABASE_KIND = "planetscale-database";
|
|
18135
18865
|
function createPlanetscaleResolveTarget(graph, config) {
|
|
18136
18866
|
const databaseName = `${config.organization}/${config.database}`;
|
|
18137
18867
|
return (signal, _ctx) => {
|
|
18138
18868
|
if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
18139
|
-
const tableId =
|
|
18869
|
+
const tableId = infraId25("sql-table", signal.targetName);
|
|
18140
18870
|
if (graph.hasNode(tableId)) {
|
|
18141
18871
|
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: EdgeType33.CALLS };
|
|
18142
18872
|
}
|
|
18143
|
-
const providerId =
|
|
18873
|
+
const providerId = infraId25(PLANETSCALE_DATABASE_KIND, databaseName);
|
|
18144
18874
|
return {
|
|
18145
18875
|
targetNodeId: providerId,
|
|
18146
18876
|
serviceName: config.serviceName,
|
|
@@ -20040,4 +20770,4 @@ export {
|
|
|
20040
20770
|
deprovisionConnector,
|
|
20041
20771
|
buildApi
|
|
20042
20772
|
};
|
|
20043
|
-
//# sourceMappingURL=chunk-
|
|
20773
|
+
//# sourceMappingURL=chunk-UN7VFA4H.js.map
|