@neat.is/core 0.7.6 → 0.7.8
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-6H757ZNM.js → chunk-4GCV46AP.js} +2 -2
- package/dist/{chunk-XT4NNFH6.js → chunk-LN75Z624.js} +866 -216
- package/dist/chunk-LN75Z624.js.map +1 -0
- package/dist/{chunk-XHSK4BGL.js → chunk-OVRVDSUO.js} +3 -3
- package/dist/{chunk-P2ZEKJ35.js → chunk-Y43UCVZS.js} +9 -6
- package/dist/chunk-Y43UCVZS.js.map +1 -0
- package/dist/cli.cjs +1244 -590
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/index.cjs +1046 -392
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +1055 -401
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-APVZSB6W.js → otel-grpc-DPSAGRUU.js} +3 -3
- package/dist/server.cjs +978 -324
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-P2ZEKJ35.js.map +0 -1
- package/dist/chunk-XT4NNFH6.js.map +0 -1
- /package/dist/{chunk-6H757ZNM.js.map → chunk-4GCV46AP.js.map} +0 -0
- /package/dist/{chunk-XHSK4BGL.js.map → chunk-OVRVDSUO.js.map} +0 -0
- /package/dist/{otel-grpc-APVZSB6W.js.map → otel-grpc-DPSAGRUU.js.map} +0 -0
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
mountBearerAuth,
|
|
4
4
|
readAuthEnv,
|
|
5
5
|
tableFromSqlStatement
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-Y43UCVZS.js";
|
|
7
7
|
|
|
8
8
|
// src/graph.ts
|
|
9
9
|
import GraphDefault from "graphology";
|
|
@@ -1247,14 +1247,14 @@ function buildServiceHostIndex(services) {
|
|
|
1247
1247
|
}
|
|
1248
1248
|
async function walkSourceFiles(dir) {
|
|
1249
1249
|
const out = [];
|
|
1250
|
-
async function
|
|
1250
|
+
async function walk9(current) {
|
|
1251
1251
|
const entries = await fs5.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
1252
1252
|
for (const entry of entries) {
|
|
1253
1253
|
const full = path5.join(current, entry.name);
|
|
1254
1254
|
if (entry.isDirectory()) {
|
|
1255
1255
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1256
1256
|
if (await isPythonVenvDir(full)) continue;
|
|
1257
|
-
await
|
|
1257
|
+
await walk9(full);
|
|
1258
1258
|
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path5.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
|
|
1259
1259
|
// would attribute our instrumentation imports to the user's service.
|
|
1260
1260
|
!isNeatAuthoredSourceFile(entry.name)) {
|
|
@@ -1262,7 +1262,7 @@ async function walkSourceFiles(dir) {
|
|
|
1262
1262
|
}
|
|
1263
1263
|
}
|
|
1264
1264
|
}
|
|
1265
|
-
await
|
|
1265
|
+
await walk9(dir);
|
|
1266
1266
|
return out;
|
|
1267
1267
|
}
|
|
1268
1268
|
async function loadSourceFiles(dir) {
|
|
@@ -1780,8 +1780,9 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
|
1780
1780
|
"all"
|
|
1781
1781
|
]);
|
|
1782
1782
|
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
1783
|
+
var NET_HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
1783
1784
|
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
1784
|
-
function
|
|
1785
|
+
function goRouterRoutesFromSource(source, parser, framework) {
|
|
1785
1786
|
const tree = parseSource2(parser, source);
|
|
1786
1787
|
const prefixes = /* @__PURE__ */ new Map();
|
|
1787
1788
|
const out = [];
|
|
@@ -1791,10 +1792,12 @@ function ginRoutesFromSource(source, parser) {
|
|
|
1791
1792
|
const value = node.childForFieldName("right")?.namedChild(0) ?? node.childForFieldName("value");
|
|
1792
1793
|
if (name && value?.type === "call_expression") {
|
|
1793
1794
|
const fn2 = value.childForFieldName("function");
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1795
|
+
if (fn2?.childForFieldName("field")?.text === "Group") {
|
|
1796
|
+
const leaf2 = goStringLiteral(value.childForFieldName("arguments")?.namedChild(0));
|
|
1797
|
+
if (leaf2 !== null) {
|
|
1798
|
+
const parent = fn2.childForFieldName("operand")?.text ?? "";
|
|
1799
|
+
prefixes.set(name, (prefixes.get(parent) ?? "") + leaf2);
|
|
1800
|
+
}
|
|
1798
1801
|
}
|
|
1799
1802
|
}
|
|
1800
1803
|
return;
|
|
@@ -1805,18 +1808,127 @@ function ginRoutesFromSource(source, parser) {
|
|
|
1805
1808
|
const method = fn.childForFieldName("field")?.text?.toUpperCase();
|
|
1806
1809
|
if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return;
|
|
1807
1810
|
const receiver = fn.childForFieldName("operand")?.text ?? "";
|
|
1808
|
-
const
|
|
1809
|
-
if (
|
|
1810
|
-
const leaf = first.text.slice(1, -1);
|
|
1811
|
+
const leaf = goStringLiteral(node.childForFieldName("arguments")?.namedChild(0));
|
|
1812
|
+
if (leaf === null) return;
|
|
1811
1813
|
out.push({
|
|
1812
|
-
method
|
|
1814
|
+
method,
|
|
1813
1815
|
pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? "") + leaf),
|
|
1814
1816
|
line: node.startPosition.row + 1,
|
|
1815
|
-
framework
|
|
1817
|
+
framework
|
|
1818
|
+
});
|
|
1819
|
+
});
|
|
1820
|
+
return out;
|
|
1821
|
+
}
|
|
1822
|
+
function goStringLiteral(node) {
|
|
1823
|
+
if (node?.type === "interpreted_string_literal" || node?.type === "raw_string_literal") {
|
|
1824
|
+
return node.text.slice(1, -1);
|
|
1825
|
+
}
|
|
1826
|
+
return null;
|
|
1827
|
+
}
|
|
1828
|
+
function ginRoutesFromSource(source, parser) {
|
|
1829
|
+
return goRouterRoutesFromSource(source, parser, "gin");
|
|
1830
|
+
}
|
|
1831
|
+
function echoRoutesFromSource(source, parser) {
|
|
1832
|
+
return goRouterRoutesFromSource(source, parser, "echo");
|
|
1833
|
+
}
|
|
1834
|
+
function fiberRoutesFromSource(source, parser) {
|
|
1835
|
+
return goRouterRoutesFromSource(source, parser, "fiber");
|
|
1836
|
+
}
|
|
1837
|
+
function chiRoutesFromSource(source, parser) {
|
|
1838
|
+
const tree = parseSource2(parser, source);
|
|
1839
|
+
const out = [];
|
|
1840
|
+
chiWalk(tree.rootNode, "", out);
|
|
1841
|
+
return out;
|
|
1842
|
+
}
|
|
1843
|
+
function stripChiRegex(path64) {
|
|
1844
|
+
return path64.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
|
|
1845
|
+
}
|
|
1846
|
+
function chiWalk(node, prefix, out) {
|
|
1847
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1848
|
+
const child = node.namedChild(i);
|
|
1849
|
+
if (child) chiHandle(child, prefix, out);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
function chiHandle(node, prefix, out) {
|
|
1853
|
+
if (node.type === "call_expression") {
|
|
1854
|
+
const fn = node.childForFieldName("function");
|
|
1855
|
+
if (fn?.type === "selector_expression") {
|
|
1856
|
+
const field = fn.childForFieldName("field")?.text;
|
|
1857
|
+
const args = node.childForFieldName("arguments");
|
|
1858
|
+
if (field === "Route") {
|
|
1859
|
+
const leaf = goStringLiteral(args?.namedChild(0));
|
|
1860
|
+
const closure = args?.namedChild(1);
|
|
1861
|
+
if (leaf !== null && closure?.type === "func_literal") {
|
|
1862
|
+
const body = closure.childForFieldName("body");
|
|
1863
|
+
if (body) chiWalk(body, prefix + leaf, out);
|
|
1864
|
+
}
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
if (field === "Group") {
|
|
1868
|
+
const closure = args?.namedChild(0);
|
|
1869
|
+
if (closure?.type === "func_literal") {
|
|
1870
|
+
const body = closure.childForFieldName("body");
|
|
1871
|
+
if (body) chiWalk(body, prefix, out);
|
|
1872
|
+
}
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
if (field === "Mount") {
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
if (field && ROUTER_METHODS.has(field.toLowerCase())) {
|
|
1879
|
+
const leaf = goStringLiteral(args?.namedChild(0));
|
|
1880
|
+
if (leaf !== null) {
|
|
1881
|
+
out.push({
|
|
1882
|
+
method: field.toUpperCase(),
|
|
1883
|
+
pathTemplate: canonicalizeTemplate(stripChiRegex(prefix + leaf)),
|
|
1884
|
+
line: node.startPosition.row + 1,
|
|
1885
|
+
framework: "chi"
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
chiWalk(node, prefix, out);
|
|
1893
|
+
}
|
|
1894
|
+
function netHttpRoutesFromSource(source, parser) {
|
|
1895
|
+
const tree = parseSource2(parser, source);
|
|
1896
|
+
if (!goImportsNetHttp(tree.rootNode)) return [];
|
|
1897
|
+
const out = [];
|
|
1898
|
+
walk(tree.rootNode, (node) => {
|
|
1899
|
+
if (node.type !== "call_expression") return;
|
|
1900
|
+
const fn = node.childForFieldName("function");
|
|
1901
|
+
if (fn?.type !== "selector_expression") return;
|
|
1902
|
+
const field = fn.childForFieldName("field")?.text;
|
|
1903
|
+
if (field !== "HandleFunc" && field !== "Handle") return;
|
|
1904
|
+
const leaf = goStringLiteral(node.childForFieldName("arguments")?.namedChild(0));
|
|
1905
|
+
if (leaf === null) return;
|
|
1906
|
+
const sp = leaf.indexOf(" ");
|
|
1907
|
+
if (sp < 0) return;
|
|
1908
|
+
const method = leaf.slice(0, sp);
|
|
1909
|
+
const rest = leaf.slice(sp + 1);
|
|
1910
|
+
if (!NET_HTTP_METHODS.has(method)) return;
|
|
1911
|
+
if (!rest.startsWith("/")) return;
|
|
1912
|
+
out.push({
|
|
1913
|
+
method,
|
|
1914
|
+
pathTemplate: canonicalizeTemplate(rest),
|
|
1915
|
+
line: node.startPosition.row + 1,
|
|
1916
|
+
framework: "net/http"
|
|
1816
1917
|
});
|
|
1817
1918
|
});
|
|
1818
1919
|
return out;
|
|
1819
1920
|
}
|
|
1921
|
+
function goImportsNetHttp(root) {
|
|
1922
|
+
let found = false;
|
|
1923
|
+
walk(root, (node) => {
|
|
1924
|
+
if (found || node.type !== "import_spec") return;
|
|
1925
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1926
|
+
const child = node.namedChild(i);
|
|
1927
|
+
if (goStringLiteral(child) === "net/http") found = true;
|
|
1928
|
+
}
|
|
1929
|
+
});
|
|
1930
|
+
return found;
|
|
1931
|
+
}
|
|
1820
1932
|
var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
|
|
1821
1933
|
var NESTJS_METHODS = /* @__PURE__ */ new Map([
|
|
1822
1934
|
["Get", "GET"],
|
|
@@ -2401,9 +2513,9 @@ function rubyRocketRoute(args) {
|
|
|
2401
2513
|
if (!pair || pair.type !== "pair") continue;
|
|
2402
2514
|
const k = pair.childForFieldName("key");
|
|
2403
2515
|
if (k?.type !== "string") continue;
|
|
2404
|
-
const
|
|
2405
|
-
if (
|
|
2406
|
-
return { path:
|
|
2516
|
+
const path64 = rubyLiteral(k);
|
|
2517
|
+
if (path64 === null) continue;
|
|
2518
|
+
return { path: path64, target: rubyLiteral(pair.childForFieldName("value")) };
|
|
2407
2519
|
}
|
|
2408
2520
|
return null;
|
|
2409
2521
|
}
|
|
@@ -3080,9 +3192,13 @@ async function addRoutes(graph, services) {
|
|
|
3080
3192
|
const hasFlask = deps["flask"] !== void 0;
|
|
3081
3193
|
const hasDjango = deps["django"] !== void 0;
|
|
3082
3194
|
const hasGin = deps["github.com/gin-gonic/gin"] !== void 0;
|
|
3195
|
+
const hasEcho = deps["github.com/labstack/echo/v4"] !== void 0 || deps["github.com/labstack/echo"] !== void 0;
|
|
3196
|
+
const hasFiber = deps["github.com/gofiber/fiber/v2"] !== void 0 || deps["github.com/gofiber/fiber/v3"] !== void 0;
|
|
3197
|
+
const hasChi = deps["github.com/go-chi/chi/v5"] !== void 0 || deps["github.com/go-chi/chi"] !== void 0;
|
|
3198
|
+
const isGoService = service.node.language === "go";
|
|
3083
3199
|
const hasRails = deps["rails"] !== void 0;
|
|
3084
3200
|
const hasLaravel = deps["laravel/framework"] !== void 0;
|
|
3085
|
-
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasRails && !hasLaravel)
|
|
3201
|
+
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel)
|
|
3086
3202
|
continue;
|
|
3087
3203
|
const files = await loadSourceFiles(service.dir);
|
|
3088
3204
|
const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
|
|
@@ -3106,7 +3222,12 @@ async function addRoutes(graph, services) {
|
|
|
3106
3222
|
} else if (isRb) {
|
|
3107
3223
|
routes = hasRails && relFile === "config/routes.rb" ? railsRoutesFromSource(file.content, rubyParser) : [];
|
|
3108
3224
|
} else if (isGo) {
|
|
3109
|
-
routes =
|
|
3225
|
+
if (hasGin) routes = ginRoutesFromSource(file.content, goParser);
|
|
3226
|
+
else if (hasEcho) routes = echoRoutesFromSource(file.content, goParser);
|
|
3227
|
+
else if (hasFiber) routes = fiberRoutesFromSource(file.content, goParser);
|
|
3228
|
+
else if (hasChi) routes = chiRoutesFromSource(file.content, goParser);
|
|
3229
|
+
else routes = [];
|
|
3230
|
+
routes = routes.concat(netHttpRoutesFromSource(file.content, goParser));
|
|
3110
3231
|
} else if (isPy) {
|
|
3111
3232
|
routes = hasFastapi || hasFlask ? pythonRoutesFromSource(file.content, pyParser, hasFastapi ? "fastapi" : "flask") : [];
|
|
3112
3233
|
if (hasDjango) routes = routes.concat(djangoRoutesFromSource(file.content, pyParser));
|
|
@@ -4740,19 +4861,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
4740
4861
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
4741
4862
|
let best = { path: [start], edges: [] };
|
|
4742
4863
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
4743
|
-
function step(node,
|
|
4744
|
-
if (
|
|
4745
|
-
best = { path: [...
|
|
4864
|
+
function step(node, path64, edges) {
|
|
4865
|
+
if (path64.length > best.path.length) {
|
|
4866
|
+
best = { path: [...path64], edges: [...edges] };
|
|
4746
4867
|
}
|
|
4747
|
-
if (
|
|
4868
|
+
if (path64.length - 1 >= maxDepth) return;
|
|
4748
4869
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
4749
4870
|
for (const [srcId, edge] of incoming) {
|
|
4750
4871
|
if (visited.has(srcId)) continue;
|
|
4751
4872
|
visited.add(srcId);
|
|
4752
|
-
|
|
4873
|
+
path64.push(srcId);
|
|
4753
4874
|
edges.push(edge);
|
|
4754
|
-
step(srcId,
|
|
4755
|
-
|
|
4875
|
+
step(srcId, path64, edges);
|
|
4876
|
+
path64.pop();
|
|
4756
4877
|
edges.pop();
|
|
4757
4878
|
visited.delete(srcId);
|
|
4758
4879
|
}
|
|
@@ -4760,11 +4881,11 @@ function longestIncomingWalk(graph, start, maxDepth) {
|
|
|
4760
4881
|
step(start, [start], []);
|
|
4761
4882
|
return best;
|
|
4762
4883
|
}
|
|
4763
|
-
function databaseRootCauseShape(graph, origin,
|
|
4884
|
+
function databaseRootCauseShape(graph, origin, walk9) {
|
|
4764
4885
|
const targetDb = origin;
|
|
4765
4886
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
4766
4887
|
if (candidatePairs.length === 0) return null;
|
|
4767
|
-
for (const id of
|
|
4888
|
+
for (const id of walk9.path) {
|
|
4768
4889
|
const owner = resolveOwningService(graph, id);
|
|
4769
4890
|
if (!owner) continue;
|
|
4770
4891
|
const { id: serviceId9, svc } = owner;
|
|
@@ -4791,8 +4912,8 @@ function databaseRootCauseShape(graph, origin, walk8) {
|
|
|
4791
4912
|
}
|
|
4792
4913
|
return null;
|
|
4793
4914
|
}
|
|
4794
|
-
function serviceRootCauseShape(graph, _origin,
|
|
4795
|
-
for (const id of
|
|
4915
|
+
function serviceRootCauseShape(graph, _origin, walk9) {
|
|
4916
|
+
for (const id of walk9.path) {
|
|
4796
4917
|
const owner = resolveOwningService(graph, id);
|
|
4797
4918
|
if (!owner) continue;
|
|
4798
4919
|
const { id: serviceId9, svc } = owner;
|
|
@@ -4828,15 +4949,15 @@ function serviceRootCauseShape(graph, _origin, walk8) {
|
|
|
4828
4949
|
}
|
|
4829
4950
|
return null;
|
|
4830
4951
|
}
|
|
4831
|
-
function fileRootCauseShape(graph, origin,
|
|
4952
|
+
function fileRootCauseShape(graph, origin, walk9) {
|
|
4832
4953
|
const owner = resolveOwningService(graph, origin.id);
|
|
4833
4954
|
if (!owner) return null;
|
|
4834
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
4955
|
+
return serviceRootCauseShape(graph, owner.svc, walk9);
|
|
4835
4956
|
}
|
|
4836
|
-
function symbolRootCauseShape(graph, origin,
|
|
4957
|
+
function symbolRootCauseShape(graph, origin, walk9) {
|
|
4837
4958
|
const owner = resolveOwningService(graph, origin.id);
|
|
4838
4959
|
if (!owner) return null;
|
|
4839
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
4960
|
+
return serviceRootCauseShape(graph, owner.svc, walk9);
|
|
4840
4961
|
}
|
|
4841
4962
|
var rootCauseShapes = {
|
|
4842
4963
|
[NodeType5.DatabaseNode]: databaseRootCauseShape,
|
|
@@ -4849,16 +4970,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
4849
4970
|
const origin = graph.getNodeAttributes(errorNodeId);
|
|
4850
4971
|
const shape = rootCauseShapes[origin.type];
|
|
4851
4972
|
if (shape) {
|
|
4852
|
-
const
|
|
4853
|
-
const match = shape(graph, origin,
|
|
4973
|
+
const walk9 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
|
|
4974
|
+
const match = shape(graph, origin, walk9);
|
|
4854
4975
|
if (match) {
|
|
4855
4976
|
const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
|
|
4856
4977
|
return RootCauseResultSchema.parse({
|
|
4857
4978
|
rootCauseNode: match.rootCauseNode,
|
|
4858
4979
|
rootCauseReason: reason,
|
|
4859
|
-
traversalPath:
|
|
4860
|
-
edgeProvenances:
|
|
4861
|
-
confidence: confidenceFromMix(
|
|
4980
|
+
traversalPath: walk9.path,
|
|
4981
|
+
edgeProvenances: walk9.edges.map((e) => e.provenance),
|
|
4982
|
+
confidence: confidenceFromMix(walk9.edges),
|
|
4862
4983
|
fixRecommendation: match.fixRecommendation
|
|
4863
4984
|
});
|
|
4864
4985
|
}
|
|
@@ -4959,26 +5080,26 @@ function dominantFailingCall(graph, serviceId9, visited) {
|
|
|
4959
5080
|
return best;
|
|
4960
5081
|
}
|
|
4961
5082
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
4962
|
-
const
|
|
5083
|
+
const path64 = [originServiceId];
|
|
4963
5084
|
const edges = [];
|
|
4964
5085
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
4965
5086
|
let current = originServiceId;
|
|
4966
5087
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
4967
5088
|
const hop = dominantFailingCall(graph, current, visited);
|
|
4968
5089
|
if (!hop) break;
|
|
4969
|
-
|
|
5090
|
+
path64.push(hop.nextService);
|
|
4970
5091
|
edges.push(hop.edge);
|
|
4971
5092
|
visited.add(hop.nextService);
|
|
4972
5093
|
current = hop.nextService;
|
|
4973
5094
|
}
|
|
4974
5095
|
if (edges.length === 0) return null;
|
|
4975
|
-
return { path:
|
|
5096
|
+
return { path: path64, edges, culprit: current };
|
|
4976
5097
|
}
|
|
4977
5098
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
4978
5099
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
4979
5100
|
if (!chain) return null;
|
|
4980
5101
|
const culprit = chain.culprit;
|
|
4981
|
-
const
|
|
5102
|
+
const path64 = [...chain.path];
|
|
4982
5103
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
4983
5104
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
4984
5105
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -4986,14 +5107,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
4986
5107
|
if (loc) {
|
|
4987
5108
|
let rootCauseNode = culprit;
|
|
4988
5109
|
if (loc.fileNode) {
|
|
4989
|
-
|
|
5110
|
+
path64.push(loc.fileNode);
|
|
4990
5111
|
edgeProvenances.push(Provenance6.OBSERVED);
|
|
4991
5112
|
rootCauseNode = loc.fileNode;
|
|
4992
5113
|
}
|
|
4993
5114
|
return RootCauseResultSchema.parse({
|
|
4994
5115
|
rootCauseNode,
|
|
4995
5116
|
rootCauseReason: loc.rootCauseReason,
|
|
4996
|
-
traversalPath:
|
|
5117
|
+
traversalPath: path64,
|
|
4997
5118
|
edgeProvenances,
|
|
4998
5119
|
confidence,
|
|
4999
5120
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -5005,7 +5126,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5005
5126
|
return RootCauseResultSchema.parse({
|
|
5006
5127
|
rootCauseNode: culprit,
|
|
5007
5128
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
5008
|
-
traversalPath:
|
|
5129
|
+
traversalPath: path64,
|
|
5009
5130
|
edgeProvenances,
|
|
5010
5131
|
confidence,
|
|
5011
5132
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -5243,6 +5364,13 @@ function parseGoMod(source) {
|
|
|
5243
5364
|
}
|
|
5244
5365
|
return { module, ...goVersion ? { goVersion } : {}, dependencies };
|
|
5245
5366
|
}
|
|
5367
|
+
function goFramework(deps) {
|
|
5368
|
+
if (deps["github.com/gin-gonic/gin"]) return "gin";
|
|
5369
|
+
if (deps["github.com/labstack/echo/v4"] || deps["github.com/labstack/echo"]) return "echo";
|
|
5370
|
+
if (deps["github.com/gofiber/fiber/v2"] || deps["github.com/gofiber/fiber/v3"]) return "fiber";
|
|
5371
|
+
if (deps["github.com/go-chi/chi/v5"] || deps["github.com/go-chi/chi"]) return "chi";
|
|
5372
|
+
return void 0;
|
|
5373
|
+
}
|
|
5246
5374
|
async function discoverGoService(scanPath, dir) {
|
|
5247
5375
|
let raw;
|
|
5248
5376
|
try {
|
|
@@ -5254,6 +5382,7 @@ async function discoverGoService(scanPath, dir) {
|
|
|
5254
5382
|
if (!mod) return null;
|
|
5255
5383
|
const name = mod.module.split("/").filter(Boolean).pop() ?? mod.module;
|
|
5256
5384
|
const pkg = { name, dependencies: mod.dependencies };
|
|
5385
|
+
const framework = goFramework(mod.dependencies);
|
|
5257
5386
|
const node = {
|
|
5258
5387
|
id: serviceId2(name),
|
|
5259
5388
|
type: NodeType6.ServiceNode,
|
|
@@ -5261,7 +5390,7 @@ async function discoverGoService(scanPath, dir) {
|
|
|
5261
5390
|
language: "go",
|
|
5262
5391
|
dependencies: mod.dependencies,
|
|
5263
5392
|
repoPath: path10.relative(scanPath, dir),
|
|
5264
|
-
...
|
|
5393
|
+
...framework ? { framework } : {}
|
|
5265
5394
|
};
|
|
5266
5395
|
return { pkg, dir, node };
|
|
5267
5396
|
}
|
|
@@ -6168,7 +6297,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
6168
6297
|
return best;
|
|
6169
6298
|
};
|
|
6170
6299
|
const requests = [];
|
|
6171
|
-
const
|
|
6300
|
+
const walk9 = (node) => {
|
|
6172
6301
|
if (node.type === "class_declaration" || node.type === "abstract_class_declaration" || node.type === "class") {
|
|
6173
6302
|
const self = localBySpan.get(`${node.startPosition.row + 1}:${node.endPosition.row + 1}`);
|
|
6174
6303
|
if (self && self.kind === "class") {
|
|
@@ -6214,10 +6343,10 @@ async function addSymbolEdges(graph, services) {
|
|
|
6214
6343
|
}
|
|
6215
6344
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6216
6345
|
const child = node.namedChild(i);
|
|
6217
|
-
if (child)
|
|
6346
|
+
if (child) walk9(child);
|
|
6218
6347
|
}
|
|
6219
6348
|
};
|
|
6220
|
-
|
|
6349
|
+
walk9(root);
|
|
6221
6350
|
for (const req of requests) {
|
|
6222
6351
|
const targetSid = resolveTarget(req.targetName, req.wantKind);
|
|
6223
6352
|
if (!targetSid) continue;
|
|
@@ -7237,20 +7366,20 @@ import {
|
|
|
7237
7366
|
} from "@neat.is/types";
|
|
7238
7367
|
async function walkConfigFiles(dir) {
|
|
7239
7368
|
const out = [];
|
|
7240
|
-
async function
|
|
7369
|
+
async function walk9(current) {
|
|
7241
7370
|
const entries = await fs16.readdir(current, { withFileTypes: true });
|
|
7242
7371
|
for (const entry of entries) {
|
|
7243
7372
|
const full = path28.join(current, entry.name);
|
|
7244
7373
|
if (entry.isDirectory()) {
|
|
7245
7374
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
7246
7375
|
if (await isPythonVenvDir(full)) continue;
|
|
7247
|
-
await
|
|
7376
|
+
await walk9(full);
|
|
7248
7377
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
7249
7378
|
out.push(full);
|
|
7250
7379
|
}
|
|
7251
7380
|
}
|
|
7252
7381
|
}
|
|
7253
|
-
await
|
|
7382
|
+
await walk9(dir);
|
|
7254
7383
|
return out;
|
|
7255
7384
|
}
|
|
7256
7385
|
async function addConfigNodes(graph, services, scanPath) {
|
|
@@ -7346,20 +7475,20 @@ function grpcMethodsFromProto(content, fqPackage) {
|
|
|
7346
7475
|
}
|
|
7347
7476
|
async function walkProtoFiles(dir) {
|
|
7348
7477
|
const out = [];
|
|
7349
|
-
async function
|
|
7478
|
+
async function walk9(current) {
|
|
7350
7479
|
const entries = await fs17.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
7351
7480
|
for (const entry of entries) {
|
|
7352
7481
|
const full = path29.join(current, entry.name);
|
|
7353
7482
|
if (entry.isDirectory()) {
|
|
7354
7483
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
7355
7484
|
if (await isPythonVenvDir(full)) continue;
|
|
7356
|
-
await
|
|
7485
|
+
await walk9(full);
|
|
7357
7486
|
} else if (entry.isFile() && path29.extname(entry.name) === PROTO_EXTENSION) {
|
|
7358
7487
|
out.push(full);
|
|
7359
7488
|
}
|
|
7360
7489
|
}
|
|
7361
7490
|
}
|
|
7362
|
-
await
|
|
7491
|
+
await walk9(dir);
|
|
7363
7492
|
return out;
|
|
7364
7493
|
}
|
|
7365
7494
|
async function addGrpcMethods(graph, services) {
|
|
@@ -8190,7 +8319,7 @@ function isFirestoreClientFactory(node) {
|
|
|
8190
8319
|
}
|
|
8191
8320
|
function firestoreClientVars(root) {
|
|
8192
8321
|
const vars = /* @__PURE__ */ new Set();
|
|
8193
|
-
const
|
|
8322
|
+
const walk9 = (node) => {
|
|
8194
8323
|
if (node.type === "variable_declarator") {
|
|
8195
8324
|
const name = node.childForFieldName("name");
|
|
8196
8325
|
let value = node.childForFieldName("value");
|
|
@@ -8199,9 +8328,9 @@ function firestoreClientVars(root) {
|
|
|
8199
8328
|
vars.add(name.text);
|
|
8200
8329
|
}
|
|
8201
8330
|
}
|
|
8202
|
-
for (const c of namedChildren(node))
|
|
8331
|
+
for (const c of namedChildren(node)) walk9(c);
|
|
8203
8332
|
};
|
|
8204
|
-
|
|
8333
|
+
walk9(root);
|
|
8205
8334
|
return vars;
|
|
8206
8335
|
}
|
|
8207
8336
|
function isClientExpr(node, clientVars) {
|
|
@@ -8356,7 +8485,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
8356
8485
|
}
|
|
8357
8486
|
s.add(field);
|
|
8358
8487
|
};
|
|
8359
|
-
const
|
|
8488
|
+
const walk9 = (node) => {
|
|
8360
8489
|
if (node.type === "call_expression") {
|
|
8361
8490
|
const fn = node.childForFieldName("function");
|
|
8362
8491
|
const line = node.startPosition.row + 1;
|
|
@@ -8396,9 +8525,9 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
8396
8525
|
}
|
|
8397
8526
|
}
|
|
8398
8527
|
}
|
|
8399
|
-
for (const c of namedChildren(node))
|
|
8528
|
+
for (const c of namedChildren(node)) walk9(c);
|
|
8400
8529
|
};
|
|
8401
|
-
|
|
8530
|
+
walk9(tree.rootNode);
|
|
8402
8531
|
const out = [];
|
|
8403
8532
|
for (const [collPath, line] of collLine) {
|
|
8404
8533
|
const byField = writes.get(collPath);
|
|
@@ -9189,7 +9318,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
9189
9318
|
const tree = parseSource3(parserForExt2(path41.extname(file.path)), file.content);
|
|
9190
9319
|
const out = [];
|
|
9191
9320
|
const seen = /* @__PURE__ */ new Set();
|
|
9192
|
-
const
|
|
9321
|
+
const walk9 = (node) => {
|
|
9193
9322
|
if (node.type === "call_expression") {
|
|
9194
9323
|
const fn = node.childForFieldName("function");
|
|
9195
9324
|
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
@@ -9217,9 +9346,9 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
9217
9346
|
}
|
|
9218
9347
|
}
|
|
9219
9348
|
}
|
|
9220
|
-
for (const c of namedChildren4(node))
|
|
9349
|
+
for (const c of namedChildren4(node)) walk9(c);
|
|
9221
9350
|
};
|
|
9222
|
-
|
|
9351
|
+
walk9(tree.rootNode);
|
|
9223
9352
|
return out;
|
|
9224
9353
|
}
|
|
9225
9354
|
function enclosingVarName(call) {
|
|
@@ -9241,7 +9370,7 @@ function enclosingVarName(call) {
|
|
|
9241
9370
|
function collectDrizzleTables(root) {
|
|
9242
9371
|
const tables = [];
|
|
9243
9372
|
const varToTable = /* @__PURE__ */ new Map();
|
|
9244
|
-
const
|
|
9373
|
+
const walk9 = (node) => {
|
|
9245
9374
|
if (node.type === "call_expression") {
|
|
9246
9375
|
const fn = node.childForFieldName("function");
|
|
9247
9376
|
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
@@ -9256,9 +9385,9 @@ function collectDrizzleTables(root) {
|
|
|
9256
9385
|
}
|
|
9257
9386
|
}
|
|
9258
9387
|
}
|
|
9259
|
-
for (const c of namedChildren4(node))
|
|
9388
|
+
for (const c of namedChildren4(node)) walk9(c);
|
|
9260
9389
|
};
|
|
9261
|
-
|
|
9390
|
+
walk9(root);
|
|
9262
9391
|
return { tables, varToTable };
|
|
9263
9392
|
}
|
|
9264
9393
|
function referencesTargetVar(call) {
|
|
@@ -9281,7 +9410,7 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
9281
9410
|
const seen = /* @__PURE__ */ new Set();
|
|
9282
9411
|
for (const table of tables) {
|
|
9283
9412
|
if (!table.object) continue;
|
|
9284
|
-
const
|
|
9413
|
+
const walk9 = (node) => {
|
|
9285
9414
|
if (node.type === "call_expression") {
|
|
9286
9415
|
const targetVar = referencesTargetVar(node);
|
|
9287
9416
|
const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
|
|
@@ -9302,9 +9431,9 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
9302
9431
|
}
|
|
9303
9432
|
}
|
|
9304
9433
|
}
|
|
9305
|
-
for (const c of namedChildren4(node))
|
|
9434
|
+
for (const c of namedChildren4(node)) walk9(c);
|
|
9306
9435
|
};
|
|
9307
|
-
|
|
9436
|
+
walk9(table.object);
|
|
9308
9437
|
}
|
|
9309
9438
|
return out;
|
|
9310
9439
|
}
|
|
@@ -10379,6 +10508,521 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
10379
10508
|
return out;
|
|
10380
10509
|
}
|
|
10381
10510
|
|
|
10511
|
+
// src/extract/calls/gorm.ts
|
|
10512
|
+
import path46 from "path";
|
|
10513
|
+
import Parser15 from "tree-sitter";
|
|
10514
|
+
import Go4 from "tree-sitter-go";
|
|
10515
|
+
import { infraId as infraId16 } from "@neat.is/types";
|
|
10516
|
+
var GORM_IMPORT_RE = /gorm\.io\/gorm/;
|
|
10517
|
+
var PARSE_CHUNK11 = 16384;
|
|
10518
|
+
function makeGoParser3() {
|
|
10519
|
+
const p = new Parser15();
|
|
10520
|
+
p.setLanguage(Go4);
|
|
10521
|
+
return p;
|
|
10522
|
+
}
|
|
10523
|
+
function parseSource10(parser, source) {
|
|
10524
|
+
return parser.parse(
|
|
10525
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK11)
|
|
10526
|
+
);
|
|
10527
|
+
}
|
|
10528
|
+
function walk8(node, visit) {
|
|
10529
|
+
visit(node);
|
|
10530
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
10531
|
+
const c = node.namedChild(i);
|
|
10532
|
+
if (c) walk8(c, visit);
|
|
10533
|
+
}
|
|
10534
|
+
}
|
|
10535
|
+
var COMMON_INITIALISMS = [
|
|
10536
|
+
"ASCII",
|
|
10537
|
+
"HTTPS",
|
|
10538
|
+
"UTF8",
|
|
10539
|
+
"XSRF",
|
|
10540
|
+
"HTML",
|
|
10541
|
+
"HTTP",
|
|
10542
|
+
"JSON",
|
|
10543
|
+
"UUID",
|
|
10544
|
+
"XMPP",
|
|
10545
|
+
"ACL",
|
|
10546
|
+
"API",
|
|
10547
|
+
"CPU",
|
|
10548
|
+
"CSS",
|
|
10549
|
+
"DNS",
|
|
10550
|
+
"EOF",
|
|
10551
|
+
"GUID",
|
|
10552
|
+
"LHS",
|
|
10553
|
+
"QPS",
|
|
10554
|
+
"RAM",
|
|
10555
|
+
"RHS",
|
|
10556
|
+
"RPC",
|
|
10557
|
+
"SLA",
|
|
10558
|
+
"SQL",
|
|
10559
|
+
"SSH",
|
|
10560
|
+
"TCP",
|
|
10561
|
+
"TLS",
|
|
10562
|
+
"TTL",
|
|
10563
|
+
"UDP",
|
|
10564
|
+
"UID",
|
|
10565
|
+
"URI",
|
|
10566
|
+
"URL",
|
|
10567
|
+
"UID",
|
|
10568
|
+
"XSS",
|
|
10569
|
+
"ID",
|
|
10570
|
+
"IP",
|
|
10571
|
+
"UI",
|
|
10572
|
+
"VM",
|
|
10573
|
+
"XML"
|
|
10574
|
+
].sort((a, b) => b.length - a.length);
|
|
10575
|
+
function titleCase(word) {
|
|
10576
|
+
return word.charAt(0) + word.slice(1).toLowerCase();
|
|
10577
|
+
}
|
|
10578
|
+
function replaceInitialisms(name) {
|
|
10579
|
+
let out = "";
|
|
10580
|
+
let i = 0;
|
|
10581
|
+
while (i < name.length) {
|
|
10582
|
+
let matched = false;
|
|
10583
|
+
for (const init of COMMON_INITIALISMS) {
|
|
10584
|
+
if (name.startsWith(init, i)) {
|
|
10585
|
+
out += titleCase(init);
|
|
10586
|
+
i += init.length;
|
|
10587
|
+
matched = true;
|
|
10588
|
+
break;
|
|
10589
|
+
}
|
|
10590
|
+
}
|
|
10591
|
+
if (!matched) {
|
|
10592
|
+
out += name[i];
|
|
10593
|
+
i++;
|
|
10594
|
+
}
|
|
10595
|
+
}
|
|
10596
|
+
return out;
|
|
10597
|
+
}
|
|
10598
|
+
var isUpper = (c) => c >= "A" && c <= "Z";
|
|
10599
|
+
var isDigit = (c) => c >= "0" && c <= "9";
|
|
10600
|
+
function toDBName(name) {
|
|
10601
|
+
if (name === "") return "";
|
|
10602
|
+
const value = replaceInitialisms(name);
|
|
10603
|
+
if (value.length === 1) return value.toLowerCase();
|
|
10604
|
+
let buf = "";
|
|
10605
|
+
let lastCase = false;
|
|
10606
|
+
let curCase = isUpper(value[0]);
|
|
10607
|
+
for (let i = 0; i < value.length - 1; i++) {
|
|
10608
|
+
const v = value[i];
|
|
10609
|
+
const nextCase = isUpper(value[i + 1]);
|
|
10610
|
+
const nextNumber = isDigit(value[i + 1]);
|
|
10611
|
+
if (curCase) {
|
|
10612
|
+
if (lastCase && (nextCase || nextNumber)) {
|
|
10613
|
+
buf += v.toLowerCase();
|
|
10614
|
+
} else {
|
|
10615
|
+
if (i > 0 && value[i - 1] !== "_" && lastCase !== curCase) buf += "_";
|
|
10616
|
+
buf += v.toLowerCase();
|
|
10617
|
+
}
|
|
10618
|
+
} else {
|
|
10619
|
+
buf += v;
|
|
10620
|
+
}
|
|
10621
|
+
lastCase = curCase;
|
|
10622
|
+
curCase = nextCase;
|
|
10623
|
+
}
|
|
10624
|
+
const last = value[value.length - 1];
|
|
10625
|
+
if (curCase) {
|
|
10626
|
+
if (!lastCase && value.length > 1) buf += "_";
|
|
10627
|
+
buf += last.toLowerCase();
|
|
10628
|
+
} else {
|
|
10629
|
+
buf += last;
|
|
10630
|
+
}
|
|
10631
|
+
return buf;
|
|
10632
|
+
}
|
|
10633
|
+
var UNCOUNTABLE = /* @__PURE__ */ new Set([
|
|
10634
|
+
"equipment",
|
|
10635
|
+
"information",
|
|
10636
|
+
"rice",
|
|
10637
|
+
"money",
|
|
10638
|
+
"species",
|
|
10639
|
+
"series",
|
|
10640
|
+
"fish",
|
|
10641
|
+
"sheep",
|
|
10642
|
+
"jeans",
|
|
10643
|
+
"police"
|
|
10644
|
+
]);
|
|
10645
|
+
var IRREGULAR = [
|
|
10646
|
+
["person", "people"],
|
|
10647
|
+
["man", "men"],
|
|
10648
|
+
["child", "children"],
|
|
10649
|
+
["sex", "sexes"],
|
|
10650
|
+
["move", "moves"]
|
|
10651
|
+
];
|
|
10652
|
+
var PLURAL_RULES = [
|
|
10653
|
+
[/(quiz)$/i, "$1zes"],
|
|
10654
|
+
[/^(ox)$/i, "$1en"],
|
|
10655
|
+
[/([ml])ouse$/i, "$1ice"],
|
|
10656
|
+
[/(matr|vert|ind)(?:ix|ex)$/i, "$1ices"],
|
|
10657
|
+
[/(x|ch|ss|sh)$/i, "$1es"],
|
|
10658
|
+
[/([^aeiouy]|qu)y$/i, "$1ies"],
|
|
10659
|
+
[/(hive)$/i, "$1s"],
|
|
10660
|
+
[/(?:([^f])fe|([lr])f)$/i, "$1$2ves"],
|
|
10661
|
+
[/sis$/i, "ses"],
|
|
10662
|
+
[/([ti])um$/i, "$1a"],
|
|
10663
|
+
[/([ti])a$/i, "$1a"],
|
|
10664
|
+
[/(buffal|tomat)o$/i, "$1oes"],
|
|
10665
|
+
[/(bu)s$/i, "$1ses"],
|
|
10666
|
+
[/(alias|status)$/i, "$1es"],
|
|
10667
|
+
[/(octop|vir)i$/i, "$1i"],
|
|
10668
|
+
[/(octop|vir)us$/i, "$1i"],
|
|
10669
|
+
[/(ax|test)is$/i, "$1es"],
|
|
10670
|
+
[/s$/i, "s"]
|
|
10671
|
+
];
|
|
10672
|
+
function pluralize3(word) {
|
|
10673
|
+
if (word === "") return word;
|
|
10674
|
+
const lower = word.toLowerCase();
|
|
10675
|
+
for (const u of UNCOUNTABLE) {
|
|
10676
|
+
if (lower === u || lower.endsWith("_" + u)) return word;
|
|
10677
|
+
}
|
|
10678
|
+
for (const [sing, plur] of IRREGULAR) {
|
|
10679
|
+
const re = new RegExp(sing + "$", "i");
|
|
10680
|
+
if (re.test(word)) return word.replace(re, plur);
|
|
10681
|
+
}
|
|
10682
|
+
for (const [re, rep] of PLURAL_RULES) {
|
|
10683
|
+
if (re.test(word)) return word.replace(re, rep);
|
|
10684
|
+
}
|
|
10685
|
+
return word + "s";
|
|
10686
|
+
}
|
|
10687
|
+
function deriveTableName(structName) {
|
|
10688
|
+
return pluralize3(toDBName(structName));
|
|
10689
|
+
}
|
|
10690
|
+
function stringLiteralValue(node) {
|
|
10691
|
+
if (!node) return null;
|
|
10692
|
+
if (node.type === "interpreted_string_literal" || node.type === "raw_string_literal") {
|
|
10693
|
+
const t = node.text;
|
|
10694
|
+
return t.length >= 2 ? t.slice(1, -1) : "";
|
|
10695
|
+
}
|
|
10696
|
+
return null;
|
|
10697
|
+
}
|
|
10698
|
+
function parseGormTag(tagNode) {
|
|
10699
|
+
const tag = {};
|
|
10700
|
+
if (!tagNode) return tag;
|
|
10701
|
+
let inner = tagNode.text;
|
|
10702
|
+
if (inner.length >= 2) inner = inner.slice(1, -1);
|
|
10703
|
+
if (tagNode.type === "interpreted_string_literal") inner = inner.replace(/\\"/g, '"');
|
|
10704
|
+
const m = inner.match(/gorm:"([^"]*)"/);
|
|
10705
|
+
if (!m) return tag;
|
|
10706
|
+
for (const part of m[1].split(";")) {
|
|
10707
|
+
if (part === "") continue;
|
|
10708
|
+
const idx = part.indexOf(":");
|
|
10709
|
+
const key = (idx >= 0 ? part.slice(0, idx) : part).trim().toLowerCase();
|
|
10710
|
+
const value = idx >= 0 ? part.slice(idx + 1).trim() : "";
|
|
10711
|
+
if (key === "-") tag.skip = true;
|
|
10712
|
+
else if (key === "column") tag.column = value;
|
|
10713
|
+
else if (key === "primarykey" || key === "primary_key") tag.primaryKey = true;
|
|
10714
|
+
else if (key === "foreignkey") tag.foreignKey = value;
|
|
10715
|
+
else if (key === "many2many") tag.many2many = value;
|
|
10716
|
+
else if (key === "embedded") tag.embedded = true;
|
|
10717
|
+
else if (key === "embeddedprefix") tag.embeddedPrefix = value;
|
|
10718
|
+
}
|
|
10719
|
+
return tag;
|
|
10720
|
+
}
|
|
10721
|
+
function unwrapType(typeNode) {
|
|
10722
|
+
let isSlice = false;
|
|
10723
|
+
let isPointer = false;
|
|
10724
|
+
let n = typeNode;
|
|
10725
|
+
while (n && (n.type === "slice_type" || n.type === "array_type" || n.type === "pointer_type")) {
|
|
10726
|
+
if (n.type === "slice_type" || n.type === "array_type") isSlice = true;
|
|
10727
|
+
if (n.type === "pointer_type") isPointer = true;
|
|
10728
|
+
n = n.childForFieldName("element") ?? n.namedChild(n.namedChildCount - 1);
|
|
10729
|
+
}
|
|
10730
|
+
if (!n) return { name: null, qualifier: null, isSlice, isPointer, isQualified: false };
|
|
10731
|
+
if (n.type === "type_identifier") {
|
|
10732
|
+
return { name: n.text, qualifier: null, isSlice, isPointer, isQualified: false };
|
|
10733
|
+
}
|
|
10734
|
+
if (n.type === "qualified_type") {
|
|
10735
|
+
const pkg = n.childForFieldName("package")?.text ?? n.namedChild(0)?.text ?? null;
|
|
10736
|
+
const nm = n.childForFieldName("name")?.text ?? n.namedChild(1)?.text ?? null;
|
|
10737
|
+
return { name: nm, qualifier: pkg, isSlice, isPointer, isQualified: true };
|
|
10738
|
+
}
|
|
10739
|
+
return { name: null, qualifier: null, isSlice, isPointer, isQualified: false };
|
|
10740
|
+
}
|
|
10741
|
+
function readField(fieldDecl) {
|
|
10742
|
+
const names = [];
|
|
10743
|
+
let tagNode = null;
|
|
10744
|
+
for (let i = 0; i < fieldDecl.namedChildCount; i++) {
|
|
10745
|
+
const c = fieldDecl.namedChild(i);
|
|
10746
|
+
if (!c) continue;
|
|
10747
|
+
if (c.type === "field_identifier") names.push(c.text);
|
|
10748
|
+
else if (c.type === "raw_string_literal" || c.type === "interpreted_string_literal") tagNode = c;
|
|
10749
|
+
}
|
|
10750
|
+
const typeNode = fieldDecl.childForFieldName("type");
|
|
10751
|
+
const t = unwrapType(typeNode);
|
|
10752
|
+
return {
|
|
10753
|
+
names,
|
|
10754
|
+
typeName: t.name,
|
|
10755
|
+
qualifier: t.qualifier,
|
|
10756
|
+
isSlice: t.isSlice,
|
|
10757
|
+
isPointer: t.isPointer,
|
|
10758
|
+
isQualified: t.isQualified,
|
|
10759
|
+
tag: parseGormTag(tagNode),
|
|
10760
|
+
line: fieldDecl.startPosition.row + 1
|
|
10761
|
+
};
|
|
10762
|
+
}
|
|
10763
|
+
function collectStructs(tree) {
|
|
10764
|
+
const structs = /* @__PURE__ */ new Map();
|
|
10765
|
+
walk8(tree.rootNode, (node) => {
|
|
10766
|
+
if (node.type !== "type_spec") return;
|
|
10767
|
+
const nameNode = node.childForFieldName("name");
|
|
10768
|
+
const typeNode = node.childForFieldName("type");
|
|
10769
|
+
if (!nameNode || typeNode?.type !== "struct_type") return;
|
|
10770
|
+
const list = typeNode.childForFieldName("body") ?? typeNode.namedChild(0);
|
|
10771
|
+
const fields = [];
|
|
10772
|
+
if (list && list.type === "field_declaration_list") {
|
|
10773
|
+
for (let i = 0; i < list.namedChildCount; i++) {
|
|
10774
|
+
const fd = list.namedChild(i);
|
|
10775
|
+
if (fd?.type === "field_declaration") fields.push(readField(fd));
|
|
10776
|
+
}
|
|
10777
|
+
}
|
|
10778
|
+
structs.set(nameNode.text, {
|
|
10779
|
+
name: nameNode.text,
|
|
10780
|
+
fields,
|
|
10781
|
+
line: node.startPosition.row + 1
|
|
10782
|
+
});
|
|
10783
|
+
});
|
|
10784
|
+
return structs;
|
|
10785
|
+
}
|
|
10786
|
+
var GORM_MODEL_METHODS = /* @__PURE__ */ new Set([
|
|
10787
|
+
"AutoMigrate",
|
|
10788
|
+
"Model",
|
|
10789
|
+
"Create",
|
|
10790
|
+
"Find",
|
|
10791
|
+
"First",
|
|
10792
|
+
"Take",
|
|
10793
|
+
"Last",
|
|
10794
|
+
"Save",
|
|
10795
|
+
"Delete",
|
|
10796
|
+
"Where",
|
|
10797
|
+
"FirstOrCreate",
|
|
10798
|
+
"FirstOrInit"
|
|
10799
|
+
]);
|
|
10800
|
+
function compositeStructName(arg) {
|
|
10801
|
+
let n = arg;
|
|
10802
|
+
if (n.type === "unary_expression") n = n.childForFieldName("operand") ?? n.namedChild(0);
|
|
10803
|
+
if (!n || n.type !== "composite_literal") return null;
|
|
10804
|
+
const typeNode = n.childForFieldName("type");
|
|
10805
|
+
if (!typeNode) return null;
|
|
10806
|
+
if (typeNode.type === "type_identifier") return typeNode.text;
|
|
10807
|
+
if (typeNode.type === "qualified_type") {
|
|
10808
|
+
return typeNode.childForFieldName("name")?.text ?? typeNode.namedChild(1)?.text ?? null;
|
|
10809
|
+
}
|
|
10810
|
+
return null;
|
|
10811
|
+
}
|
|
10812
|
+
function collectCallModels(tree) {
|
|
10813
|
+
const models = /* @__PURE__ */ new Set();
|
|
10814
|
+
walk8(tree.rootNode, (node) => {
|
|
10815
|
+
if (node.type !== "call_expression") return;
|
|
10816
|
+
const fn = node.childForFieldName("function");
|
|
10817
|
+
if (fn?.type !== "selector_expression") return;
|
|
10818
|
+
const method = fn.childForFieldName("field")?.text;
|
|
10819
|
+
if (!method || !GORM_MODEL_METHODS.has(method)) return;
|
|
10820
|
+
const args = node.childForFieldName("arguments");
|
|
10821
|
+
if (!args) return;
|
|
10822
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
10823
|
+
const arg = args.namedChild(i);
|
|
10824
|
+
if (!arg) continue;
|
|
10825
|
+
const name = compositeStructName(arg);
|
|
10826
|
+
if (name) models.add(name);
|
|
10827
|
+
}
|
|
10828
|
+
});
|
|
10829
|
+
return models;
|
|
10830
|
+
}
|
|
10831
|
+
function collectTableNameOverrides(tree) {
|
|
10832
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
10833
|
+
const declarers = /* @__PURE__ */ new Set();
|
|
10834
|
+
walk8(tree.rootNode, (node) => {
|
|
10835
|
+
if (node.type !== "method_declaration") return;
|
|
10836
|
+
if (node.childForFieldName("name")?.text !== "TableName") return;
|
|
10837
|
+
const receiver = node.childForFieldName("receiver");
|
|
10838
|
+
if (!receiver) return;
|
|
10839
|
+
let recvType = null;
|
|
10840
|
+
for (let i = 0; i < receiver.namedChildCount; i++) {
|
|
10841
|
+
const pd = receiver.namedChild(i);
|
|
10842
|
+
if (pd?.type !== "parameter_declaration") continue;
|
|
10843
|
+
const t = unwrapType(pd.childForFieldName("type"));
|
|
10844
|
+
recvType = t.name;
|
|
10845
|
+
}
|
|
10846
|
+
if (!recvType) return;
|
|
10847
|
+
declarers.add(recvType);
|
|
10848
|
+
const body = node.childForFieldName("body");
|
|
10849
|
+
if (!body) return;
|
|
10850
|
+
let literal = null;
|
|
10851
|
+
walk8(body, (n) => {
|
|
10852
|
+
if (literal !== null) return;
|
|
10853
|
+
if (n.type !== "return_statement") return;
|
|
10854
|
+
const exprList = n.namedChild(0);
|
|
10855
|
+
const first = exprList?.namedChild(0) ?? exprList;
|
|
10856
|
+
const v = stringLiteralValue(first);
|
|
10857
|
+
if (v) literal = v;
|
|
10858
|
+
});
|
|
10859
|
+
if (literal !== null) overrides.set(recvType, literal);
|
|
10860
|
+
});
|
|
10861
|
+
return { overrides, declarers };
|
|
10862
|
+
}
|
|
10863
|
+
function isRelationField(field, structs) {
|
|
10864
|
+
if (field.names.length === 0) return false;
|
|
10865
|
+
if (field.isQualified) return false;
|
|
10866
|
+
if (!field.typeName) return false;
|
|
10867
|
+
return structs.has(field.typeName);
|
|
10868
|
+
}
|
|
10869
|
+
function isGormModelEmbed(field) {
|
|
10870
|
+
return field.names.length === 0 && field.qualifier === "gorm" && field.typeName === "Model";
|
|
10871
|
+
}
|
|
10872
|
+
function analyze(tree) {
|
|
10873
|
+
const structs = collectStructs(tree);
|
|
10874
|
+
const { overrides, declarers } = collectTableNameOverrides(tree);
|
|
10875
|
+
const callModels = collectCallModels(tree);
|
|
10876
|
+
const models = /* @__PURE__ */ new Set();
|
|
10877
|
+
for (const [name, info] of structs) {
|
|
10878
|
+
if (info.fields.some(isGormModelEmbed)) models.add(name);
|
|
10879
|
+
}
|
|
10880
|
+
for (const name of callModels) if (structs.has(name)) models.add(name);
|
|
10881
|
+
for (const name of declarers) if (structs.has(name)) models.add(name);
|
|
10882
|
+
let grew = true;
|
|
10883
|
+
while (grew) {
|
|
10884
|
+
grew = false;
|
|
10885
|
+
for (const name of Array.from(models)) {
|
|
10886
|
+
const info = structs.get(name);
|
|
10887
|
+
if (!info) continue;
|
|
10888
|
+
for (const field of info.fields) {
|
|
10889
|
+
if (!isRelationField(field, structs)) continue;
|
|
10890
|
+
const target = field.typeName;
|
|
10891
|
+
if (!models.has(target) && structs.has(target)) {
|
|
10892
|
+
models.add(target);
|
|
10893
|
+
grew = true;
|
|
10894
|
+
}
|
|
10895
|
+
}
|
|
10896
|
+
}
|
|
10897
|
+
}
|
|
10898
|
+
const tableFor = (structName) => overrides.get(structName) ?? deriveTableName(structName);
|
|
10899
|
+
return { structs, models, tableFor };
|
|
10900
|
+
}
|
|
10901
|
+
function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
10902
|
+
if (seen.has(struct.name)) return;
|
|
10903
|
+
seen.add(struct.name);
|
|
10904
|
+
const add = (col) => {
|
|
10905
|
+
const full = prefix + col;
|
|
10906
|
+
if (!emitted.has(full)) {
|
|
10907
|
+
emitted.add(full);
|
|
10908
|
+
out.push(full);
|
|
10909
|
+
}
|
|
10910
|
+
};
|
|
10911
|
+
for (const field of struct.fields) {
|
|
10912
|
+
if (field.tag.skip) continue;
|
|
10913
|
+
if (field.names.length === 0) {
|
|
10914
|
+
if (isGormModelEmbed(field)) {
|
|
10915
|
+
add("id");
|
|
10916
|
+
add("created_at");
|
|
10917
|
+
add("updated_at");
|
|
10918
|
+
add("deleted_at");
|
|
10919
|
+
} else if (!field.isQualified && field.typeName && structs.has(field.typeName)) {
|
|
10920
|
+
collectColumns(structs.get(field.typeName), structs, seen, prefix, out, emitted);
|
|
10921
|
+
}
|
|
10922
|
+
continue;
|
|
10923
|
+
}
|
|
10924
|
+
if (field.tag.embedded && !field.isQualified && field.typeName && structs.has(field.typeName)) {
|
|
10925
|
+
collectColumns(
|
|
10926
|
+
structs.get(field.typeName),
|
|
10927
|
+
structs,
|
|
10928
|
+
seen,
|
|
10929
|
+
prefix + (field.tag.embeddedPrefix ?? ""),
|
|
10930
|
+
out,
|
|
10931
|
+
emitted
|
|
10932
|
+
);
|
|
10933
|
+
continue;
|
|
10934
|
+
}
|
|
10935
|
+
if (isRelationField(field, structs)) continue;
|
|
10936
|
+
if (field.names.length === 1 && field.tag.column) {
|
|
10937
|
+
add(field.tag.column);
|
|
10938
|
+
} else {
|
|
10939
|
+
for (const n of field.names) add(toDBName(n));
|
|
10940
|
+
}
|
|
10941
|
+
}
|
|
10942
|
+
seen.delete(struct.name);
|
|
10943
|
+
}
|
|
10944
|
+
function gormEndpointsFromFile(file, serviceDir) {
|
|
10945
|
+
if (path46.extname(file.path) !== ".go") return [];
|
|
10946
|
+
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
10947
|
+
const tree = parseSource10(makeGoParser3(), file.content);
|
|
10948
|
+
const { structs, models, tableFor } = analyze(tree);
|
|
10949
|
+
const out = [];
|
|
10950
|
+
const seenTables = /* @__PURE__ */ new Set();
|
|
10951
|
+
for (const name of models) {
|
|
10952
|
+
const struct = structs.get(name);
|
|
10953
|
+
if (!struct) continue;
|
|
10954
|
+
const table = tableFor(name);
|
|
10955
|
+
if (seenTables.has(table)) continue;
|
|
10956
|
+
seenTables.add(table);
|
|
10957
|
+
const columns = [];
|
|
10958
|
+
collectColumns(struct, structs, /* @__PURE__ */ new Set(), "", columns, /* @__PURE__ */ new Set());
|
|
10959
|
+
out.push({
|
|
10960
|
+
infraId: infraId16("sql-table", table),
|
|
10961
|
+
name: table,
|
|
10962
|
+
kind: "sql-table",
|
|
10963
|
+
edgeType: "CALLS",
|
|
10964
|
+
confidenceKind: "structural",
|
|
10965
|
+
...columns.length > 0 ? { columns } : {},
|
|
10966
|
+
evidence: {
|
|
10967
|
+
file: toPosix(path46.relative(serviceDir, file.path)),
|
|
10968
|
+
line: struct.line,
|
|
10969
|
+
snippet: snippet(file.content, struct.line)
|
|
10970
|
+
}
|
|
10971
|
+
});
|
|
10972
|
+
}
|
|
10973
|
+
return out;
|
|
10974
|
+
}
|
|
10975
|
+
function gormForeignKeys(file, serviceDir) {
|
|
10976
|
+
if (path46.extname(file.path) !== ".go") return [];
|
|
10977
|
+
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
10978
|
+
const tree = parseSource10(makeGoParser3(), file.content);
|
|
10979
|
+
const { structs, models, tableFor } = analyze(tree);
|
|
10980
|
+
const out = [];
|
|
10981
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10982
|
+
const emit = (childTable, parentTable, line) => {
|
|
10983
|
+
if (!childTable || !parentTable || childTable === parentTable) return;
|
|
10984
|
+
const key = `${childTable}->${parentTable}`;
|
|
10985
|
+
if (seen.has(key)) return;
|
|
10986
|
+
seen.add(key);
|
|
10987
|
+
out.push({
|
|
10988
|
+
childTable,
|
|
10989
|
+
parentTable,
|
|
10990
|
+
evidence: {
|
|
10991
|
+
file: toPosix(path46.relative(serviceDir, file.path)),
|
|
10992
|
+
line,
|
|
10993
|
+
snippet: snippet(file.content, line)
|
|
10994
|
+
}
|
|
10995
|
+
});
|
|
10996
|
+
};
|
|
10997
|
+
for (const name of models) {
|
|
10998
|
+
const struct = structs.get(name);
|
|
10999
|
+
if (!struct) continue;
|
|
11000
|
+
const thisTable = tableFor(name);
|
|
11001
|
+
const scalarNames = new Set(
|
|
11002
|
+
struct.fields.filter((f) => f.names.length > 0 && !isRelationField(f, structs)).flatMap((f) => f.names)
|
|
11003
|
+
);
|
|
11004
|
+
for (const field of struct.fields) {
|
|
11005
|
+
if (field.tag.skip) continue;
|
|
11006
|
+
if (!isRelationField(field, structs)) continue;
|
|
11007
|
+
const relTable = tableFor(field.typeName);
|
|
11008
|
+
if (field.tag.many2many) {
|
|
11009
|
+
emit(field.tag.many2many, thisTable, field.line);
|
|
11010
|
+
emit(field.tag.many2many, relTable, field.line);
|
|
11011
|
+
continue;
|
|
11012
|
+
}
|
|
11013
|
+
if (field.isSlice) {
|
|
11014
|
+
emit(relTable, thisTable, field.line);
|
|
11015
|
+
continue;
|
|
11016
|
+
}
|
|
11017
|
+
const convFk = field.names[0] + "ID";
|
|
11018
|
+
const belongsTo = scalarNames.has(convFk) || (field.tag.foreignKey ? scalarNames.has(field.tag.foreignKey) : false);
|
|
11019
|
+
if (belongsTo) emit(thisTable, relTable, field.line);
|
|
11020
|
+
else emit(relTable, thisTable, field.line);
|
|
11021
|
+
}
|
|
11022
|
+
}
|
|
11023
|
+
return out;
|
|
11024
|
+
}
|
|
11025
|
+
|
|
10382
11026
|
// src/extract/calls/index.ts
|
|
10383
11027
|
function edgeTypeFromEndpoint(ep) {
|
|
10384
11028
|
switch (ep.edgeType) {
|
|
@@ -10420,6 +11064,11 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
10420
11064
|
} catch (err) {
|
|
10421
11065
|
recordExtractionError("go SQL call extraction", file.path, err);
|
|
10422
11066
|
}
|
|
11067
|
+
try {
|
|
11068
|
+
endpoints.push(...gormEndpointsFromFile(file, service.dir));
|
|
11069
|
+
} catch (err) {
|
|
11070
|
+
recordExtractionError("gorm data-axis extraction", file.path, err);
|
|
11071
|
+
}
|
|
10423
11072
|
try {
|
|
10424
11073
|
endpoints.push(...railsSchemaEndpointsFromFile(file, service.dir));
|
|
10425
11074
|
endpoints.push(...railsModelEndpointsFromFile(file, service.dir));
|
|
@@ -10535,7 +11184,7 @@ import {
|
|
|
10535
11184
|
Provenance as Provenance16,
|
|
10536
11185
|
confidenceForExtracted as confidenceForExtracted13,
|
|
10537
11186
|
extractedEdgeId as extractedEdgeId10,
|
|
10538
|
-
infraId as
|
|
11187
|
+
infraId as infraId17
|
|
10539
11188
|
} from "@neat.is/types";
|
|
10540
11189
|
async function addTableEdges(graph, services) {
|
|
10541
11190
|
let nodesAdded = 0;
|
|
@@ -10550,6 +11199,7 @@ async function addTableEdges(graph, services) {
|
|
|
10550
11199
|
refs.push(...sqlalchemyForeignKeys(file, service.dir));
|
|
10551
11200
|
refs.push(...railsSchemaForeignKeys(file, service.dir));
|
|
10552
11201
|
refs.push(...laravelMigrationForeignKeys(file, service.dir));
|
|
11202
|
+
refs.push(...gormForeignKeys(file, service.dir));
|
|
10553
11203
|
modelRefs.push(...railsModelForeignKeys(file, service.dir));
|
|
10554
11204
|
modelRefs.push(...laravelModelForeignKeys(file, service.dir));
|
|
10555
11205
|
} catch (err) {
|
|
@@ -10563,8 +11213,8 @@ async function addTableEdges(graph, services) {
|
|
|
10563
11213
|
}
|
|
10564
11214
|
refs.push(...modelRefs);
|
|
10565
11215
|
for (const ref of refs) {
|
|
10566
|
-
const childId =
|
|
10567
|
-
const parentId =
|
|
11216
|
+
const childId = infraId17("sql-table", ref.childTable);
|
|
11217
|
+
const parentId = infraId17("sql-table", ref.parentTable);
|
|
10568
11218
|
if (childId === parentId) continue;
|
|
10569
11219
|
nodesAdded += ensureTableNode(graph, childId, ref.childTable);
|
|
10570
11220
|
nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
|
|
@@ -10599,14 +11249,14 @@ function ensureTableNode(graph, id, name) {
|
|
|
10599
11249
|
}
|
|
10600
11250
|
|
|
10601
11251
|
// src/extract/infra/docker-compose.ts
|
|
10602
|
-
import
|
|
11252
|
+
import path47 from "path";
|
|
10603
11253
|
import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
|
|
10604
11254
|
|
|
10605
11255
|
// src/extract/infra/shared.ts
|
|
10606
|
-
import { NodeType as NodeType20, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as
|
|
11256
|
+
import { NodeType as NodeType20, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
|
|
10607
11257
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
10608
11258
|
return {
|
|
10609
|
-
id:
|
|
11259
|
+
id: infraId18(kind, name),
|
|
10610
11260
|
type: NodeType20.InfraNode,
|
|
10611
11261
|
name,
|
|
10612
11262
|
provider,
|
|
@@ -10669,7 +11319,7 @@ function dependsOnList(value) {
|
|
|
10669
11319
|
}
|
|
10670
11320
|
function serviceNameToServiceNode(name, services) {
|
|
10671
11321
|
for (const s of services) {
|
|
10672
|
-
if (s.node.name === name ||
|
|
11322
|
+
if (s.node.name === name || path47.basename(s.dir) === name) return s.node.id;
|
|
10673
11323
|
}
|
|
10674
11324
|
return null;
|
|
10675
11325
|
}
|
|
@@ -10678,7 +11328,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
10678
11328
|
let edgesAdded = 0;
|
|
10679
11329
|
let composePath = null;
|
|
10680
11330
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
10681
|
-
const abs =
|
|
11331
|
+
const abs = path47.join(scanPath, name);
|
|
10682
11332
|
if (await exists(abs)) {
|
|
10683
11333
|
composePath = abs;
|
|
10684
11334
|
break;
|
|
@@ -10691,13 +11341,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
10691
11341
|
} catch (err) {
|
|
10692
11342
|
recordExtractionError(
|
|
10693
11343
|
"infra docker-compose",
|
|
10694
|
-
|
|
11344
|
+
path47.relative(scanPath, composePath),
|
|
10695
11345
|
err
|
|
10696
11346
|
);
|
|
10697
11347
|
return { nodesAdded, edgesAdded };
|
|
10698
11348
|
}
|
|
10699
11349
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
10700
|
-
const evidenceFile =
|
|
11350
|
+
const evidenceFile = path47.relative(scanPath, composePath).split(path47.sep).join("/");
|
|
10701
11351
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
10702
11352
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
10703
11353
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -10738,7 +11388,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
10738
11388
|
}
|
|
10739
11389
|
|
|
10740
11390
|
// src/extract/infra/dockerfile.ts
|
|
10741
|
-
import
|
|
11391
|
+
import path48 from "path";
|
|
10742
11392
|
import { promises as fs18 } from "fs";
|
|
10743
11393
|
import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
|
|
10744
11394
|
function readDockerfile(content) {
|
|
@@ -10769,7 +11419,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
10769
11419
|
let nodesAdded = 0;
|
|
10770
11420
|
let edgesAdded = 0;
|
|
10771
11421
|
for (const service of services) {
|
|
10772
|
-
const dockerfilePath =
|
|
11422
|
+
const dockerfilePath = path48.join(service.dir, "Dockerfile");
|
|
10773
11423
|
if (!await exists(dockerfilePath)) continue;
|
|
10774
11424
|
let content;
|
|
10775
11425
|
try {
|
|
@@ -10777,7 +11427,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
10777
11427
|
} catch (err) {
|
|
10778
11428
|
recordExtractionError(
|
|
10779
11429
|
"infra dockerfile",
|
|
10780
|
-
|
|
11430
|
+
path48.relative(scanPath, dockerfilePath),
|
|
10781
11431
|
err
|
|
10782
11432
|
);
|
|
10783
11433
|
continue;
|
|
@@ -10789,8 +11439,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
10789
11439
|
graph.addNode(node.id, node);
|
|
10790
11440
|
nodesAdded++;
|
|
10791
11441
|
}
|
|
10792
|
-
const relDockerfile = toPosix(
|
|
10793
|
-
const evidenceFile = toPosix(
|
|
11442
|
+
const relDockerfile = toPosix(path48.relative(service.dir, dockerfilePath));
|
|
11443
|
+
const evidenceFile = toPosix(path48.relative(scanPath, dockerfilePath));
|
|
10794
11444
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
10795
11445
|
graph,
|
|
10796
11446
|
service.pkg.name,
|
|
@@ -10842,7 +11492,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
10842
11492
|
|
|
10843
11493
|
// src/extract/infra/terraform.ts
|
|
10844
11494
|
import { promises as fs19 } from "fs";
|
|
10845
|
-
import
|
|
11495
|
+
import path49 from "path";
|
|
10846
11496
|
import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
|
|
10847
11497
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
10848
11498
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
@@ -10853,11 +11503,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
|
|
|
10853
11503
|
for (const entry of entries) {
|
|
10854
11504
|
if (entry.isDirectory()) {
|
|
10855
11505
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
10856
|
-
const child =
|
|
11506
|
+
const child = path49.join(start, entry.name);
|
|
10857
11507
|
if (await isPythonVenvDir(child)) continue;
|
|
10858
11508
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
10859
11509
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
10860
|
-
out.push(
|
|
11510
|
+
out.push(path49.join(start, entry.name));
|
|
10861
11511
|
}
|
|
10862
11512
|
}
|
|
10863
11513
|
return out;
|
|
@@ -10889,7 +11539,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
10889
11539
|
const files = await walkTfFiles(scanPath);
|
|
10890
11540
|
for (const file of files) {
|
|
10891
11541
|
const content = await fs19.readFile(file, "utf8");
|
|
10892
|
-
const evidenceFile = toPosix(
|
|
11542
|
+
const evidenceFile = toPosix(path49.relative(scanPath, file));
|
|
10893
11543
|
const resources = [];
|
|
10894
11544
|
const byKey = /* @__PURE__ */ new Map();
|
|
10895
11545
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -10946,7 +11596,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
10946
11596
|
|
|
10947
11597
|
// src/extract/infra/k8s.ts
|
|
10948
11598
|
import { promises as fs20 } from "fs";
|
|
10949
|
-
import
|
|
11599
|
+
import path50 from "path";
|
|
10950
11600
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
10951
11601
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
10952
11602
|
Service: "k8s-service",
|
|
@@ -10964,11 +11614,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
|
10964
11614
|
for (const entry of entries) {
|
|
10965
11615
|
if (entry.isDirectory()) {
|
|
10966
11616
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
10967
|
-
const child =
|
|
11617
|
+
const child = path50.join(start, entry.name);
|
|
10968
11618
|
if (await isPythonVenvDir(child)) continue;
|
|
10969
11619
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
10970
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
10971
|
-
out.push(
|
|
11620
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path50.extname(entry.name))) {
|
|
11621
|
+
out.push(path50.join(start, entry.name));
|
|
10972
11622
|
}
|
|
10973
11623
|
}
|
|
10974
11624
|
return out;
|
|
@@ -11001,13 +11651,13 @@ async function addK8sResources(graph, scanPath) {
|
|
|
11001
11651
|
|
|
11002
11652
|
// src/extract/infra/cloudflare.ts
|
|
11003
11653
|
import { promises as fs21 } from "fs";
|
|
11004
|
-
import
|
|
11654
|
+
import path51 from "path";
|
|
11005
11655
|
import { parse as parseToml2 } from "smol-toml";
|
|
11006
11656
|
import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
|
|
11007
11657
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
11008
11658
|
async function readWranglerConfig(dir) {
|
|
11009
11659
|
for (const filename of WRANGLER_FILENAMES) {
|
|
11010
|
-
const abs =
|
|
11660
|
+
const abs = path51.join(dir, filename);
|
|
11011
11661
|
if (!await exists(abs)) continue;
|
|
11012
11662
|
const raw = await fs21.readFile(abs, "utf8");
|
|
11013
11663
|
const config = filename === "wrangler.toml" ? parseToml2(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
@@ -11070,11 +11720,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
11070
11720
|
try {
|
|
11071
11721
|
read = await readWranglerConfig(service.dir);
|
|
11072
11722
|
} catch (err) {
|
|
11073
|
-
recordExtractionError("infra cloudflare",
|
|
11723
|
+
recordExtractionError("infra cloudflare", path51.relative(scanPath, service.dir), err);
|
|
11074
11724
|
continue;
|
|
11075
11725
|
}
|
|
11076
11726
|
if (!read || !read.config.name) continue;
|
|
11077
|
-
const evidenceFile = toPosix(
|
|
11727
|
+
const evidenceFile = toPosix(path51.relative(scanPath, path51.join(service.dir, read.relFile)));
|
|
11078
11728
|
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
11079
11729
|
}
|
|
11080
11730
|
for (const worker of discovered) {
|
|
@@ -11086,7 +11736,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
11086
11736
|
}
|
|
11087
11737
|
let anchorId = service.node.id;
|
|
11088
11738
|
if (config.main) {
|
|
11089
|
-
const entryRelPath = toPosix(
|
|
11739
|
+
const entryRelPath = toPosix(path51.normalize(config.main));
|
|
11090
11740
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
11091
11741
|
graph,
|
|
11092
11742
|
service.pkg.name,
|
|
@@ -11233,12 +11883,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
11233
11883
|
|
|
11234
11884
|
// src/extract/infra/vercel.ts
|
|
11235
11885
|
import { promises as fs22 } from "fs";
|
|
11236
|
-
import
|
|
11886
|
+
import path52 from "path";
|
|
11237
11887
|
import { EdgeType as EdgeType21 } from "@neat.is/types";
|
|
11238
11888
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
11239
11889
|
async function readVercelConfig(dir) {
|
|
11240
11890
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
11241
|
-
const abs =
|
|
11891
|
+
const abs = path52.join(dir, filename);
|
|
11242
11892
|
if (!await exists(abs)) continue;
|
|
11243
11893
|
const raw = await fs22.readFile(abs, "utf8");
|
|
11244
11894
|
const config = JSON.parse(maskCommentsInSource(raw));
|
|
@@ -11247,7 +11897,7 @@ async function readVercelConfig(dir) {
|
|
|
11247
11897
|
return null;
|
|
11248
11898
|
}
|
|
11249
11899
|
async function readLinkedProjectName(dir) {
|
|
11250
|
-
const abs =
|
|
11900
|
+
const abs = path52.join(dir, ".vercel", "project.json");
|
|
11251
11901
|
if (!await exists(abs)) return void 0;
|
|
11252
11902
|
const parsed = JSON.parse(await fs22.readFile(abs, "utf8"));
|
|
11253
11903
|
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
@@ -11265,7 +11915,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
11265
11915
|
read = await readVercelConfig(service.dir);
|
|
11266
11916
|
projectName = await readLinkedProjectName(service.dir);
|
|
11267
11917
|
} catch (err) {
|
|
11268
|
-
recordExtractionError("infra vercel",
|
|
11918
|
+
recordExtractionError("infra vercel", path52.relative(scanPath, service.dir), err);
|
|
11269
11919
|
continue;
|
|
11270
11920
|
}
|
|
11271
11921
|
if (!read && !projectName) continue;
|
|
@@ -11281,7 +11931,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
11281
11931
|
const anchorId = service.node.id;
|
|
11282
11932
|
if (!read) continue;
|
|
11283
11933
|
const { config, relFile, raw } = read;
|
|
11284
|
-
const evidenceFile = toPosix(
|
|
11934
|
+
const evidenceFile = toPosix(path52.relative(scanPath, path52.join(service.dir, relFile)));
|
|
11285
11935
|
const add = (edgeType, kind, name) => {
|
|
11286
11936
|
if (!name) return;
|
|
11287
11937
|
const result = emitPlatformResourceEdge(
|
|
@@ -11310,13 +11960,13 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
11310
11960
|
|
|
11311
11961
|
// src/extract/infra/railway.ts
|
|
11312
11962
|
import { promises as fs23 } from "fs";
|
|
11313
|
-
import
|
|
11963
|
+
import path53 from "path";
|
|
11314
11964
|
import { parse as parseToml3 } from "smol-toml";
|
|
11315
11965
|
import { EdgeType as EdgeType22 } from "@neat.is/types";
|
|
11316
11966
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
11317
11967
|
async function readRailwayConfig(dir) {
|
|
11318
11968
|
for (const filename of RAILWAY_FILENAMES) {
|
|
11319
|
-
const abs =
|
|
11969
|
+
const abs = path53.join(dir, filename);
|
|
11320
11970
|
if (!await exists(abs)) continue;
|
|
11321
11971
|
const raw = await fs23.readFile(abs, "utf8");
|
|
11322
11972
|
const config = filename === "railway.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
@@ -11332,7 +11982,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
11332
11982
|
try {
|
|
11333
11983
|
read = await readRailwayConfig(service.dir);
|
|
11334
11984
|
} catch (err) {
|
|
11335
|
-
recordExtractionError("infra railway",
|
|
11985
|
+
recordExtractionError("infra railway", path53.relative(scanPath, service.dir), err);
|
|
11336
11986
|
continue;
|
|
11337
11987
|
}
|
|
11338
11988
|
if (!read) continue;
|
|
@@ -11342,7 +11992,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
11342
11992
|
}
|
|
11343
11993
|
const anchorId = service.node.id;
|
|
11344
11994
|
const { config, relFile, raw } = read;
|
|
11345
|
-
const evidenceFile = toPosix(
|
|
11995
|
+
const evidenceFile = toPosix(path53.relative(scanPath, path53.join(service.dir, relFile)));
|
|
11346
11996
|
const add = (edgeType, kind, name) => {
|
|
11347
11997
|
if (!name) return;
|
|
11348
11998
|
const result = emitPlatformResourceEdge(
|
|
@@ -11367,12 +12017,12 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
11367
12017
|
|
|
11368
12018
|
// src/extract/infra/supabase.ts
|
|
11369
12019
|
import { promises as fs24 } from "fs";
|
|
11370
|
-
import
|
|
12020
|
+
import path54 from "path";
|
|
11371
12021
|
import { parse as parseToml4 } from "smol-toml";
|
|
11372
12022
|
import { EdgeType as EdgeType23 } from "@neat.is/types";
|
|
11373
12023
|
async function readSupabaseConfig(dir) {
|
|
11374
|
-
const relFile =
|
|
11375
|
-
const abs =
|
|
12024
|
+
const relFile = path54.join("supabase", "config.toml");
|
|
12025
|
+
const abs = path54.join(dir, relFile);
|
|
11376
12026
|
if (!await exists(abs)) return null;
|
|
11377
12027
|
const raw = await fs24.readFile(abs, "utf8");
|
|
11378
12028
|
const config = parseToml4(raw);
|
|
@@ -11386,7 +12036,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
11386
12036
|
try {
|
|
11387
12037
|
read = await readSupabaseConfig(service.dir);
|
|
11388
12038
|
} catch (err) {
|
|
11389
|
-
recordExtractionError("infra supabase",
|
|
12039
|
+
recordExtractionError("infra supabase", path54.relative(scanPath, service.dir), err);
|
|
11390
12040
|
continue;
|
|
11391
12041
|
}
|
|
11392
12042
|
if (!read) continue;
|
|
@@ -11401,7 +12051,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
11401
12051
|
});
|
|
11402
12052
|
}
|
|
11403
12053
|
const anchorId = service.node.id;
|
|
11404
|
-
const evidenceFile = toPosix(
|
|
12054
|
+
const evidenceFile = toPosix(path54.relative(scanPath, path54.join(service.dir, relFile)));
|
|
11405
12055
|
const add = (edgeType, kind, name) => {
|
|
11406
12056
|
if (!name) return;
|
|
11407
12057
|
const result = emitPlatformResourceEdge(
|
|
@@ -11442,8 +12092,8 @@ async function addInfra(graph, scanPath, services) {
|
|
|
11442
12092
|
}
|
|
11443
12093
|
|
|
11444
12094
|
// src/extract/zod-shapes.ts
|
|
11445
|
-
import
|
|
11446
|
-
import
|
|
12095
|
+
import path55 from "path";
|
|
12096
|
+
import Parser16 from "tree-sitter";
|
|
11447
12097
|
import JavaScript8 from "tree-sitter-javascript";
|
|
11448
12098
|
import {
|
|
11449
12099
|
EdgeType as EdgeType24,
|
|
@@ -11451,12 +12101,12 @@ import {
|
|
|
11451
12101
|
Provenance as Provenance22,
|
|
11452
12102
|
confidenceForExtracted as confidenceForExtracted19,
|
|
11453
12103
|
extractedEdgeId as extractedEdgeId11,
|
|
11454
|
-
infraId as
|
|
12104
|
+
infraId as infraId19
|
|
11455
12105
|
} from "@neat.is/types";
|
|
11456
12106
|
var ZOD_IMPORT_RE = /\bzod\b/;
|
|
11457
12107
|
var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
|
|
11458
12108
|
function parserForExt3(ext) {
|
|
11459
|
-
const p = new
|
|
12109
|
+
const p = new Parser16();
|
|
11460
12110
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript8);
|
|
11461
12111
|
return p;
|
|
11462
12112
|
}
|
|
@@ -11544,7 +12194,7 @@ function topLevelSchemas(root) {
|
|
|
11544
12194
|
}
|
|
11545
12195
|
function zodShapesFromFile(file, serviceDir) {
|
|
11546
12196
|
if (!ZOD_IMPORT_RE.test(file.content)) return [];
|
|
11547
|
-
const tree = parseSource3(parserForExt3(
|
|
12197
|
+
const tree = parseSource3(parserForExt3(path55.extname(file.path)), file.content);
|
|
11548
12198
|
const out = [];
|
|
11549
12199
|
const seen = /* @__PURE__ */ new Set();
|
|
11550
12200
|
for (const { name, call } of topLevelSchemas(tree.rootNode)) {
|
|
@@ -11558,11 +12208,11 @@ function zodShapesFromFile(file, serviceDir) {
|
|
|
11558
12208
|
seen.add(name);
|
|
11559
12209
|
const line = call.startPosition.row + 1;
|
|
11560
12210
|
out.push({
|
|
11561
|
-
infraId:
|
|
12211
|
+
infraId: infraId19("zod-schema", name),
|
|
11562
12212
|
name,
|
|
11563
12213
|
fields,
|
|
11564
12214
|
evidence: {
|
|
11565
|
-
file:
|
|
12215
|
+
file: path55.relative(serviceDir, file.path),
|
|
11566
12216
|
line,
|
|
11567
12217
|
snippet: snippet(file.content, line)
|
|
11568
12218
|
}
|
|
@@ -11798,11 +12448,11 @@ async function addFirestoreRules(graph, services) {
|
|
|
11798
12448
|
}
|
|
11799
12449
|
|
|
11800
12450
|
// src/extract/index.ts
|
|
11801
|
-
import
|
|
12451
|
+
import path57 from "path";
|
|
11802
12452
|
|
|
11803
12453
|
// src/extract/retire.ts
|
|
11804
12454
|
import { existsSync as existsSync2 } from "fs";
|
|
11805
|
-
import
|
|
12455
|
+
import path56 from "path";
|
|
11806
12456
|
import { NodeType as NodeType23, Provenance as Provenance23 } from "@neat.is/types";
|
|
11807
12457
|
function dropOrphanedFileNodes(graph) {
|
|
11808
12458
|
const orphans = [];
|
|
@@ -11836,11 +12486,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
11836
12486
|
if (edge.provenance !== Provenance23.EXTRACTED) return;
|
|
11837
12487
|
const evidenceFile = edge.evidence?.file;
|
|
11838
12488
|
if (!evidenceFile) return;
|
|
11839
|
-
if (
|
|
12489
|
+
if (path56.isAbsolute(evidenceFile)) {
|
|
11840
12490
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
11841
12491
|
return;
|
|
11842
12492
|
}
|
|
11843
|
-
const found = bases.some((base) => existsSync2(
|
|
12493
|
+
const found = bases.some((base) => existsSync2(path56.join(base, evidenceFile)));
|
|
11844
12494
|
if (!found) toDrop.push(id);
|
|
11845
12495
|
});
|
|
11846
12496
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -11897,7 +12547,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
11897
12547
|
}
|
|
11898
12548
|
const droppedEntries = drainDroppedExtracted();
|
|
11899
12549
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
11900
|
-
const rejectedPath =
|
|
12550
|
+
const rejectedPath = path57.join(path57.dirname(opts.errorsPath), "rejected.ndjson");
|
|
11901
12551
|
try {
|
|
11902
12552
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
11903
12553
|
} catch (err) {
|
|
@@ -12272,7 +12922,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
12272
12922
|
|
|
12273
12923
|
// src/persist.ts
|
|
12274
12924
|
import { promises as fs25 } from "fs";
|
|
12275
|
-
import
|
|
12925
|
+
import path58 from "path";
|
|
12276
12926
|
import { NodeType as NodeType25, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
12277
12927
|
var SCHEMA_VERSION = 7;
|
|
12278
12928
|
function migrateV1ToV2(payload) {
|
|
@@ -12327,7 +12977,7 @@ function migrateV2ToV3(payload) {
|
|
|
12327
12977
|
return { ...payload, schemaVersion: 3 };
|
|
12328
12978
|
}
|
|
12329
12979
|
async function ensureDir(filePath) {
|
|
12330
|
-
await fs25.mkdir(
|
|
12980
|
+
await fs25.mkdir(path58.dirname(filePath), { recursive: true });
|
|
12331
12981
|
}
|
|
12332
12982
|
async function saveGraphToDisk(graph, outPath) {
|
|
12333
12983
|
await ensureDir(outPath);
|
|
@@ -12492,23 +13142,23 @@ function canonicalJson(value) {
|
|
|
12492
13142
|
}
|
|
12493
13143
|
|
|
12494
13144
|
// src/projects.ts
|
|
12495
|
-
import
|
|
13145
|
+
import path59 from "path";
|
|
12496
13146
|
function pathsForProject(project, baseDir) {
|
|
12497
13147
|
if (project === DEFAULT_PROJECT) {
|
|
12498
13148
|
return {
|
|
12499
|
-
snapshotPath:
|
|
12500
|
-
errorsPath:
|
|
12501
|
-
staleEventsPath:
|
|
12502
|
-
embeddingsCachePath:
|
|
12503
|
-
policyViolationsPath:
|
|
13149
|
+
snapshotPath: path59.join(baseDir, "graph.json"),
|
|
13150
|
+
errorsPath: path59.join(baseDir, "errors.ndjson"),
|
|
13151
|
+
staleEventsPath: path59.join(baseDir, "stale-events.ndjson"),
|
|
13152
|
+
embeddingsCachePath: path59.join(baseDir, "embeddings.json"),
|
|
13153
|
+
policyViolationsPath: path59.join(baseDir, "policy-violations.ndjson")
|
|
12504
13154
|
};
|
|
12505
13155
|
}
|
|
12506
13156
|
return {
|
|
12507
|
-
snapshotPath:
|
|
12508
|
-
errorsPath:
|
|
12509
|
-
staleEventsPath:
|
|
12510
|
-
embeddingsCachePath:
|
|
12511
|
-
policyViolationsPath:
|
|
13157
|
+
snapshotPath: path59.join(baseDir, `${project}.json`),
|
|
13158
|
+
errorsPath: path59.join(baseDir, `errors.${project}.ndjson`),
|
|
13159
|
+
staleEventsPath: path59.join(baseDir, `stale-events.${project}.ndjson`),
|
|
13160
|
+
embeddingsCachePath: path59.join(baseDir, `embeddings.${project}.json`),
|
|
13161
|
+
policyViolationsPath: path59.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
12512
13162
|
};
|
|
12513
13163
|
}
|
|
12514
13164
|
var Projects = class {
|
|
@@ -12549,7 +13199,7 @@ function parseExtraProjects(raw) {
|
|
|
12549
13199
|
// src/registry.ts
|
|
12550
13200
|
import { promises as fs27 } from "fs";
|
|
12551
13201
|
import os2 from "os";
|
|
12552
|
-
import
|
|
13202
|
+
import path60 from "path";
|
|
12553
13203
|
import {
|
|
12554
13204
|
RegistryFileSchema
|
|
12555
13205
|
} from "@neat.is/types";
|
|
@@ -12557,20 +13207,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
12557
13207
|
var LOCK_RETRY_MS = 50;
|
|
12558
13208
|
function neatHome() {
|
|
12559
13209
|
const override = process.env.NEAT_HOME;
|
|
12560
|
-
if (override && override.length > 0) return
|
|
12561
|
-
return
|
|
13210
|
+
if (override && override.length > 0) return path60.resolve(override);
|
|
13211
|
+
return path60.join(os2.homedir(), ".neat");
|
|
12562
13212
|
}
|
|
12563
13213
|
function registryPath() {
|
|
12564
|
-
return
|
|
13214
|
+
return path60.join(neatHome(), "projects.json");
|
|
12565
13215
|
}
|
|
12566
13216
|
function registryLockPath() {
|
|
12567
|
-
return
|
|
13217
|
+
return path60.join(neatHome(), "projects.json.lock");
|
|
12568
13218
|
}
|
|
12569
13219
|
function daemonPidPath() {
|
|
12570
|
-
return
|
|
13220
|
+
return path60.join(neatHome(), "neatd.pid");
|
|
12571
13221
|
}
|
|
12572
13222
|
function daemonsDir() {
|
|
12573
|
-
return
|
|
13223
|
+
return path60.join(neatHome(), "daemons");
|
|
12574
13224
|
}
|
|
12575
13225
|
function isFiniteInt(v) {
|
|
12576
13226
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -12611,7 +13261,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
12611
13261
|
const out = [];
|
|
12612
13262
|
for (const name of names) {
|
|
12613
13263
|
if (!name.endsWith(".json")) continue;
|
|
12614
|
-
const file =
|
|
13264
|
+
const file = path60.join(dir, name);
|
|
12615
13265
|
let raw;
|
|
12616
13266
|
try {
|
|
12617
13267
|
raw = await fs27.readFile(file, "utf8");
|
|
@@ -12732,7 +13382,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
12732
13382
|
}
|
|
12733
13383
|
}
|
|
12734
13384
|
async function normalizeProjectPath(input) {
|
|
12735
|
-
const resolved =
|
|
13385
|
+
const resolved = path60.resolve(input);
|
|
12736
13386
|
try {
|
|
12737
13387
|
return await fs27.realpath(resolved);
|
|
12738
13388
|
} catch {
|
|
@@ -12740,7 +13390,7 @@ async function normalizeProjectPath(input) {
|
|
|
12740
13390
|
}
|
|
12741
13391
|
}
|
|
12742
13392
|
async function writeAtomically(target, contents) {
|
|
12743
|
-
await fs27.mkdir(
|
|
13393
|
+
await fs27.mkdir(path60.dirname(target), { recursive: true });
|
|
12744
13394
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
12745
13395
|
const fd = await fs27.open(tmp, "w");
|
|
12746
13396
|
try {
|
|
@@ -12753,7 +13403,7 @@ async function writeAtomically(target, contents) {
|
|
|
12753
13403
|
}
|
|
12754
13404
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
12755
13405
|
const deadline = Date.now() + timeoutMs;
|
|
12756
|
-
await fs27.mkdir(
|
|
13406
|
+
await fs27.mkdir(path60.dirname(lockPath), { recursive: true });
|
|
12757
13407
|
let probedHolder = false;
|
|
12758
13408
|
while (true) {
|
|
12759
13409
|
try {
|
|
@@ -12949,13 +13599,13 @@ import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } f
|
|
|
12949
13599
|
|
|
12950
13600
|
// src/extend/index.ts
|
|
12951
13601
|
import { promises as fs29 } from "fs";
|
|
12952
|
-
import
|
|
13602
|
+
import path62 from "path";
|
|
12953
13603
|
import os3 from "os";
|
|
12954
13604
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
12955
13605
|
|
|
12956
13606
|
// src/installers/package-manager.ts
|
|
12957
13607
|
import { promises as fs28 } from "fs";
|
|
12958
|
-
import
|
|
13608
|
+
import path61 from "path";
|
|
12959
13609
|
import { spawn } from "child_process";
|
|
12960
13610
|
var LOCKFILE_PRIORITY = [
|
|
12961
13611
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -12977,22 +13627,22 @@ async function exists2(p) {
|
|
|
12977
13627
|
}
|
|
12978
13628
|
}
|
|
12979
13629
|
async function detectPackageManager(serviceDir) {
|
|
12980
|
-
let dir =
|
|
13630
|
+
let dir = path61.resolve(serviceDir);
|
|
12981
13631
|
const stops = /* @__PURE__ */ new Set();
|
|
12982
13632
|
for (let i = 0; i < 64; i++) {
|
|
12983
13633
|
if (stops.has(dir)) break;
|
|
12984
13634
|
stops.add(dir);
|
|
12985
13635
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
12986
|
-
const lockPath =
|
|
13636
|
+
const lockPath = path61.join(dir, candidate.lockfile);
|
|
12987
13637
|
if (await exists2(lockPath)) {
|
|
12988
13638
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
12989
13639
|
}
|
|
12990
13640
|
}
|
|
12991
|
-
const parent =
|
|
13641
|
+
const parent = path61.dirname(dir);
|
|
12992
13642
|
if (parent === dir) break;
|
|
12993
13643
|
dir = parent;
|
|
12994
13644
|
}
|
|
12995
|
-
return { pm: "npm", cwd:
|
|
13645
|
+
return { pm: "npm", cwd: path61.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
12996
13646
|
}
|
|
12997
13647
|
async function runPackageManagerInstall(cmd) {
|
|
12998
13648
|
return new Promise((resolve) => {
|
|
@@ -13041,7 +13691,7 @@ async function fileExists2(p) {
|
|
|
13041
13691
|
}
|
|
13042
13692
|
}
|
|
13043
13693
|
async function readPackageJson(scanPath) {
|
|
13044
|
-
const pkgPath =
|
|
13694
|
+
const pkgPath = path62.join(scanPath, "package.json");
|
|
13045
13695
|
const raw = await fs29.readFile(pkgPath, "utf8");
|
|
13046
13696
|
return JSON.parse(raw);
|
|
13047
13697
|
}
|
|
@@ -13055,27 +13705,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
13055
13705
|
]);
|
|
13056
13706
|
async function findHookFiles(scanPath) {
|
|
13057
13707
|
const found = [];
|
|
13058
|
-
const
|
|
13708
|
+
const walk9 = async (dir) => {
|
|
13059
13709
|
const entries = await fs29.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
13060
13710
|
for (const entry of entries) {
|
|
13061
13711
|
if (entry.isDirectory()) {
|
|
13062
13712
|
if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
|
|
13063
|
-
await
|
|
13713
|
+
await walk9(path62.join(dir, entry.name));
|
|
13064
13714
|
} else if (entry.isFile()) {
|
|
13065
13715
|
if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
|
|
13066
|
-
const rel =
|
|
13067
|
-
found.push(rel.split(
|
|
13716
|
+
const rel = path62.relative(scanPath, path62.join(dir, entry.name));
|
|
13717
|
+
found.push(rel.split(path62.sep).join("/"));
|
|
13068
13718
|
}
|
|
13069
13719
|
}
|
|
13070
13720
|
}
|
|
13071
13721
|
};
|
|
13072
|
-
await
|
|
13722
|
+
await walk9(scanPath);
|
|
13073
13723
|
return found.sort();
|
|
13074
13724
|
}
|
|
13075
13725
|
async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
13076
13726
|
let fallback = null;
|
|
13077
13727
|
for (const file of hookFiles) {
|
|
13078
|
-
const content = await fs29.readFile(
|
|
13728
|
+
const content = await fs29.readFile(path62.join(scanPath, file), "utf8");
|
|
13079
13729
|
const patched = splicedContent(content, snippet2);
|
|
13080
13730
|
if (patched !== null) return { file, content, patched };
|
|
13081
13731
|
if (fallback === null) fallback = { file, content };
|
|
@@ -13083,11 +13733,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
|
13083
13733
|
return { file: fallback.file, content: fallback.content, patched: null };
|
|
13084
13734
|
}
|
|
13085
13735
|
function extendLogPath() {
|
|
13086
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
13736
|
+
return process.env.NEAT_EXTEND_LOG ?? path62.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
13087
13737
|
}
|
|
13088
13738
|
async function appendExtendLog(entry) {
|
|
13089
13739
|
const logPath = extendLogPath();
|
|
13090
|
-
await fs29.mkdir(
|
|
13740
|
+
await fs29.mkdir(path62.dirname(logPath), { recursive: true });
|
|
13091
13741
|
await fs29.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
13092
13742
|
}
|
|
13093
13743
|
function splicedContent(fileContent, snippet2) {
|
|
@@ -13146,7 +13796,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
13146
13796
|
}
|
|
13147
13797
|
async function describeProjectInstrumentation(ctx) {
|
|
13148
13798
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
13149
|
-
const envNeat = await fileExists2(
|
|
13799
|
+
const envNeat = await fileExists2(path62.join(ctx.scanPath, ".env.neat"));
|
|
13150
13800
|
const registryInstrPackages = new Set(
|
|
13151
13801
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
13152
13802
|
);
|
|
@@ -13168,7 +13818,7 @@ async function applyExtension(ctx, args, options) {
|
|
|
13168
13818
|
);
|
|
13169
13819
|
}
|
|
13170
13820
|
for (const file of hookFiles) {
|
|
13171
|
-
const content = await fs29.readFile(
|
|
13821
|
+
const content = await fs29.readFile(path62.join(ctx.scanPath, file), "utf8");
|
|
13172
13822
|
if (content.includes(args.registration_snippet)) {
|
|
13173
13823
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
13174
13824
|
}
|
|
@@ -13180,10 +13830,10 @@ async function applyExtension(ctx, args, options) {
|
|
|
13180
13830
|
);
|
|
13181
13831
|
}
|
|
13182
13832
|
const primaryFile = primary.file;
|
|
13183
|
-
const primaryPath =
|
|
13833
|
+
const primaryPath = path62.join(ctx.scanPath, primaryFile);
|
|
13184
13834
|
const filesTouched = [];
|
|
13185
13835
|
const depsAdded = [];
|
|
13186
|
-
const pkgPath =
|
|
13836
|
+
const pkgPath = path62.join(ctx.scanPath, "package.json");
|
|
13187
13837
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
13188
13838
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
13189
13839
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
@@ -13222,7 +13872,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
13222
13872
|
};
|
|
13223
13873
|
}
|
|
13224
13874
|
for (const file of hookFiles) {
|
|
13225
|
-
const content = await fs29.readFile(
|
|
13875
|
+
const content = await fs29.readFile(path62.join(ctx.scanPath, file), "utf8");
|
|
13226
13876
|
if (content.includes(args.registration_snippet)) {
|
|
13227
13877
|
return {
|
|
13228
13878
|
library: args.library,
|
|
@@ -13263,7 +13913,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
13263
13913
|
if (!match) {
|
|
13264
13914
|
return { undone: false, message: "no apply found for library" };
|
|
13265
13915
|
}
|
|
13266
|
-
const pkgPath =
|
|
13916
|
+
const pkgPath = path62.join(ctx.scanPath, "package.json");
|
|
13267
13917
|
if (await fileExists2(pkgPath)) {
|
|
13268
13918
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
13269
13919
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
@@ -13274,7 +13924,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
13274
13924
|
}
|
|
13275
13925
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
13276
13926
|
for (const file of hookFiles) {
|
|
13277
|
-
const filePath =
|
|
13927
|
+
const filePath = path62.join(ctx.scanPath, file);
|
|
13278
13928
|
const content = await fs29.readFile(filePath, "utf8");
|
|
13279
13929
|
if (content.includes(match.registration_snippet)) {
|
|
13280
13930
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
@@ -13389,7 +14039,7 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
13389
14039
|
|
|
13390
14040
|
// src/connectors-config.ts
|
|
13391
14041
|
import os4 from "os";
|
|
13392
|
-
import
|
|
14042
|
+
import path63 from "path";
|
|
13393
14043
|
import { promises as fs30 } from "fs";
|
|
13394
14044
|
var CONNECTORS_CONFIG_VERSION = 1;
|
|
13395
14045
|
var EnvRefUnsetError = class extends Error {
|
|
@@ -13404,11 +14054,11 @@ var EnvRefUnsetError = class extends Error {
|
|
|
13404
14054
|
};
|
|
13405
14055
|
function neatHome2() {
|
|
13406
14056
|
const override = process.env.NEAT_HOME;
|
|
13407
|
-
if (override && override.length > 0) return
|
|
13408
|
-
return
|
|
14057
|
+
if (override && override.length > 0) return path63.resolve(override);
|
|
14058
|
+
return path63.join(os4.homedir(), ".neat");
|
|
13409
14059
|
}
|
|
13410
14060
|
function connectorsConfigPath(home = neatHome2()) {
|
|
13411
|
-
return
|
|
14061
|
+
return path63.join(home, "connectors.json");
|
|
13412
14062
|
}
|
|
13413
14063
|
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
13414
14064
|
async function warnIfModeLooserThan0600(file) {
|
|
@@ -13539,7 +14189,7 @@ function connectorMatchesProject(entry, project) {
|
|
|
13539
14189
|
var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
|
|
13540
14190
|
var CONNECTORS_LOCK_RETRY_MS = 50;
|
|
13541
14191
|
function connectorsConfigLockPath(home = neatHome2()) {
|
|
13542
|
-
return
|
|
14192
|
+
return path63.join(home, "connectors.json.lock");
|
|
13543
14193
|
}
|
|
13544
14194
|
function isEnvRef(value) {
|
|
13545
14195
|
return value.length > 1 && value.startsWith("$");
|
|
@@ -13552,7 +14202,7 @@ function redactCredentialRef(ref) {
|
|
|
13552
14202
|
return out;
|
|
13553
14203
|
}
|
|
13554
14204
|
async function writeConfigAtomically0600(file, contents) {
|
|
13555
|
-
await fs30.mkdir(
|
|
14205
|
+
await fs30.mkdir(path63.dirname(file), { recursive: true });
|
|
13556
14206
|
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
13557
14207
|
const fd = await fs30.open(tmp, "w", 384);
|
|
13558
14208
|
try {
|
|
@@ -13566,7 +14216,7 @@ async function writeConfigAtomically0600(file, contents) {
|
|
|
13566
14216
|
}
|
|
13567
14217
|
async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
|
|
13568
14218
|
const deadline = Date.now() + timeoutMs;
|
|
13569
|
-
await fs30.mkdir(
|
|
14219
|
+
await fs30.mkdir(path63.dirname(lockPath), { recursive: true });
|
|
13570
14220
|
for (; ; ) {
|
|
13571
14221
|
try {
|
|
13572
14222
|
const fd = await fs30.open(lockPath, "wx");
|
|
@@ -14201,10 +14851,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
14201
14851
|
// src/connectors/supabase/map.ts
|
|
14202
14852
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
14203
14853
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
14204
|
-
function targetFromRestPath(
|
|
14205
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
14854
|
+
function targetFromRestPath(path64) {
|
|
14855
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path64);
|
|
14206
14856
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
14207
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
14857
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path64);
|
|
14208
14858
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
14209
14859
|
return null;
|
|
14210
14860
|
}
|
|
@@ -14313,21 +14963,21 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
14313
14963
|
}
|
|
14314
14964
|
|
|
14315
14965
|
// src/connectors/supabase/resolve.ts
|
|
14316
|
-
import { EdgeType as EdgeType26, infraId as
|
|
14966
|
+
import { EdgeType as EdgeType26, infraId as infraId20 } from "@neat.is/types";
|
|
14317
14967
|
function createSupabaseResolveTarget(graph, config) {
|
|
14318
14968
|
return (signal, _ctx) => {
|
|
14319
14969
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
14320
14970
|
return null;
|
|
14321
14971
|
}
|
|
14322
|
-
const subResourceId =
|
|
14972
|
+
const subResourceId = infraId20(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
14323
14973
|
if (graph.hasNode(subResourceId)) {
|
|
14324
14974
|
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
|
|
14325
14975
|
}
|
|
14326
|
-
const bareResourceId =
|
|
14976
|
+
const bareResourceId = infraId20(signal.targetKind, signal.targetName);
|
|
14327
14977
|
if (graph.hasNode(bareResourceId)) {
|
|
14328
14978
|
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
|
|
14329
14979
|
}
|
|
14330
|
-
const projectLevelId =
|
|
14980
|
+
const projectLevelId = infraId20("supabase", config.nodeRef);
|
|
14331
14981
|
if (graph.hasNode(projectLevelId)) {
|
|
14332
14982
|
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
|
|
14333
14983
|
}
|
|
@@ -14799,9 +15449,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
14799
15449
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
14800
15450
|
if (secondSep === -1) return null;
|
|
14801
15451
|
const method = rest.slice(0, secondSep);
|
|
14802
|
-
const
|
|
14803
|
-
if (!resourceName || !method || !
|
|
14804
|
-
return { resourceName, method, path:
|
|
15452
|
+
const path64 = rest.slice(secondSep + 1);
|
|
15453
|
+
if (!resourceName || !method || !path64) return null;
|
|
15454
|
+
return { resourceName, method, path: path64 };
|
|
14805
15455
|
}
|
|
14806
15456
|
function resourceNameFor(type, labels) {
|
|
14807
15457
|
if (!labels) return null;
|
|
@@ -14839,14 +15489,14 @@ function mapLogEntryToSignal(entry) {
|
|
|
14839
15489
|
if (!req) return null;
|
|
14840
15490
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
14841
15491
|
const method = req.requestMethod.toUpperCase();
|
|
14842
|
-
const
|
|
14843
|
-
if (
|
|
15492
|
+
const path64 = pathFromRequestUrl(req.requestUrl);
|
|
15493
|
+
if (path64 === null) return null;
|
|
14844
15494
|
const timestamp = entry.timestamp;
|
|
14845
15495
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
14846
15496
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
14847
15497
|
return {
|
|
14848
15498
|
targetKind: resourceType,
|
|
14849
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
15499
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path64 }),
|
|
14850
15500
|
callCount: 1,
|
|
14851
15501
|
errorCount: isError ? 1 : 0,
|
|
14852
15502
|
lastObservedIso: timestamp
|
|
@@ -14932,7 +15582,7 @@ function createFirebaseConnector(graph, serviceMap) {
|
|
|
14932
15582
|
}
|
|
14933
15583
|
|
|
14934
15584
|
// src/connectors/cloudflare/connector.ts
|
|
14935
|
-
import { EdgeType as EdgeType29, NodeType as NodeType29, fileId as fileId4, infraId as
|
|
15585
|
+
import { EdgeType as EdgeType29, NodeType as NodeType29, fileId as fileId4, infraId as infraId21 } from "@neat.is/types";
|
|
14936
15586
|
|
|
14937
15587
|
// src/connectors/cloudflare/client.ts
|
|
14938
15588
|
import { randomUUID } from "crypto";
|
|
@@ -15043,7 +15693,7 @@ function mapEventToSignal(event) {
|
|
|
15043
15693
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
15044
15694
|
const statusCode = metadata?.statusCode;
|
|
15045
15695
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
15046
|
-
const
|
|
15696
|
+
const path64 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
15047
15697
|
return {
|
|
15048
15698
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
15049
15699
|
targetName: scriptName,
|
|
@@ -15051,7 +15701,7 @@ function mapEventToSignal(event) {
|
|
|
15051
15701
|
errorCount: isError ? 1 : 0,
|
|
15052
15702
|
lastObservedIso: observedAt.toISOString(),
|
|
15053
15703
|
method,
|
|
15054
|
-
...
|
|
15704
|
+
...path64 ? { path: path64 } : {},
|
|
15055
15705
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
15056
15706
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
15057
15707
|
};
|
|
@@ -15097,8 +15747,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
15097
15747
|
});
|
|
15098
15748
|
return found;
|
|
15099
15749
|
}
|
|
15100
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
15101
|
-
const normalizedPath = normalizePathTemplate(
|
|
15750
|
+
function findMatchingRouteNode(graph, serviceName, method, path64) {
|
|
15751
|
+
const normalizedPath = normalizePathTemplate(path64);
|
|
15102
15752
|
let found = null;
|
|
15103
15753
|
graph.forEachNode((id, attrs) => {
|
|
15104
15754
|
if (found) return;
|
|
@@ -15115,10 +15765,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
15115
15765
|
return (signal) => {
|
|
15116
15766
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
15117
15767
|
const scriptName = signal.targetName;
|
|
15118
|
-
const { method, path:
|
|
15768
|
+
const { method, path: path64 } = signal;
|
|
15119
15769
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
15120
|
-
if (!method || !
|
|
15121
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
15770
|
+
if (!method || !path64) return wholeFileId;
|
|
15771
|
+
return findMatchingRouteNode(graph, serviceName, method, path64) ?? wholeFileId;
|
|
15122
15772
|
};
|
|
15123
15773
|
const mapping = config.workers?.[scriptName];
|
|
15124
15774
|
if (mapping) {
|
|
@@ -15139,7 +15789,7 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
15139
15789
|
};
|
|
15140
15790
|
}
|
|
15141
15791
|
return {
|
|
15142
|
-
targetNodeId:
|
|
15792
|
+
targetNodeId: infraId21("cloudflare-worker", scriptName),
|
|
15143
15793
|
serviceName: scriptName,
|
|
15144
15794
|
edgeType: EdgeType29.CALLS,
|
|
15145
15795
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
@@ -15323,12 +15973,12 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
15323
15973
|
}
|
|
15324
15974
|
|
|
15325
15975
|
// src/connectors/neon/resolve.ts
|
|
15326
|
-
import { EdgeType as EdgeType30, infraId as
|
|
15976
|
+
import { EdgeType as EdgeType30, infraId as infraId22 } from "@neat.is/types";
|
|
15327
15977
|
function createNeonResolveTarget(config) {
|
|
15328
15978
|
return (signal) => {
|
|
15329
15979
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
15330
15980
|
return {
|
|
15331
|
-
targetNodeId:
|
|
15981
|
+
targetNodeId: infraId22("sql-table", signal.targetName),
|
|
15332
15982
|
serviceName: config.serviceName,
|
|
15333
15983
|
edgeType: EdgeType30.CALLS,
|
|
15334
15984
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
@@ -15448,9 +16098,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
15448
16098
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
15449
16099
|
if (secondSep === -1) return null;
|
|
15450
16100
|
const method = rest.slice(0, secondSep);
|
|
15451
|
-
const
|
|
15452
|
-
if (!serviceName || !method || !
|
|
15453
|
-
return { serviceName, method, path:
|
|
16101
|
+
const path64 = rest.slice(secondSep + 1);
|
|
16102
|
+
if (!serviceName || !method || !path64) return null;
|
|
16103
|
+
return { serviceName, method, path: path64 };
|
|
15454
16104
|
}
|
|
15455
16105
|
|
|
15456
16106
|
// src/connectors/cloud-run/map.ts
|
|
@@ -15479,14 +16129,14 @@ function mapLogEntryToSignal2(entry) {
|
|
|
15479
16129
|
if (!req) return null;
|
|
15480
16130
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
15481
16131
|
const method = req.requestMethod.toUpperCase();
|
|
15482
|
-
const
|
|
15483
|
-
if (
|
|
16132
|
+
const path64 = pathFromRequestUrl2(req.requestUrl);
|
|
16133
|
+
if (path64 === null) return null;
|
|
15484
16134
|
const timestamp = entry.timestamp;
|
|
15485
16135
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
15486
16136
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
15487
16137
|
return {
|
|
15488
16138
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
15489
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
16139
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path64 }),
|
|
15490
16140
|
callCount: 1,
|
|
15491
16141
|
errorCount: isError ? 1 : 0,
|
|
15492
16142
|
lastObservedIso: timestamp
|
|
@@ -15502,7 +16152,7 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
15502
16152
|
}
|
|
15503
16153
|
|
|
15504
16154
|
// src/connectors/cloud-run/resolve.ts
|
|
15505
|
-
import { EdgeType as EdgeType31, NodeType as NodeType30, infraId as
|
|
16155
|
+
import { EdgeType as EdgeType31, NodeType as NodeType30, infraId as infraId23 } from "@neat.is/types";
|
|
15506
16156
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
15507
16157
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
15508
16158
|
let found = null;
|
|
@@ -15524,21 +16174,21 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
15524
16174
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
15525
16175
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
15526
16176
|
if (!identity) return null;
|
|
15527
|
-
const { serviceName: gcpServiceName, method, path:
|
|
16177
|
+
const { serviceName: gcpServiceName, method, path: path64 } = identity;
|
|
15528
16178
|
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
15529
16179
|
if (mappedService) {
|
|
15530
16180
|
const routeNodeId = findMatchingRouteNode2(
|
|
15531
16181
|
graph,
|
|
15532
16182
|
mappedService,
|
|
15533
16183
|
method,
|
|
15534
|
-
normalizePathTemplate(
|
|
16184
|
+
normalizePathTemplate(path64)
|
|
15535
16185
|
);
|
|
15536
16186
|
if (routeNodeId) {
|
|
15537
16187
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
|
|
15538
16188
|
}
|
|
15539
16189
|
}
|
|
15540
16190
|
return {
|
|
15541
|
-
targetNodeId:
|
|
16191
|
+
targetNodeId: infraId23(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
15542
16192
|
serviceName: mappedService ?? gcpServiceName,
|
|
15543
16193
|
edgeType: EdgeType31.CALLS,
|
|
15544
16194
|
ensureInfraNode: {
|
|
@@ -15870,17 +16520,17 @@ function mapInsightsToSignals(rows, observedAtIso) {
|
|
|
15870
16520
|
}
|
|
15871
16521
|
|
|
15872
16522
|
// src/connectors/planetscale/resolve.ts
|
|
15873
|
-
import { EdgeType as EdgeType33, infraId as
|
|
16523
|
+
import { EdgeType as EdgeType33, infraId as infraId24 } from "@neat.is/types";
|
|
15874
16524
|
var PLANETSCALE_DATABASE_KIND = "planetscale-database";
|
|
15875
16525
|
function createPlanetscaleResolveTarget(graph, config) {
|
|
15876
16526
|
const databaseName = `${config.organization}/${config.database}`;
|
|
15877
16527
|
return (signal, _ctx) => {
|
|
15878
16528
|
if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
15879
|
-
const tableId =
|
|
16529
|
+
const tableId = infraId24("sql-table", signal.targetName);
|
|
15880
16530
|
if (graph.hasNode(tableId)) {
|
|
15881
16531
|
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: EdgeType33.CALLS };
|
|
15882
16532
|
}
|
|
15883
|
-
const providerId =
|
|
16533
|
+
const providerId = infraId24(PLANETSCALE_DATABASE_KIND, databaseName);
|
|
15884
16534
|
return {
|
|
15885
16535
|
targetNodeId: providerId,
|
|
15886
16536
|
serviceName: config.serviceName,
|
|
@@ -17385,4 +18035,4 @@ export {
|
|
|
17385
18035
|
deprovisionConnector,
|
|
17386
18036
|
buildApi
|
|
17387
18037
|
};
|
|
17388
|
-
//# sourceMappingURL=chunk-
|
|
18038
|
+
//# sourceMappingURL=chunk-LN75Z624.js.map
|