@neat.is/core 0.4.6 → 0.4.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-RBWL4HRB.js → chunk-J5CEKCTR.js} +337 -133
- package/dist/chunk-J5CEKCTR.js.map +1 -0
- package/dist/{chunk-NON5AXOR.js → chunk-RC3CIDZO.js} +2 -2
- package/dist/cli.cjs +501 -164
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +2 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +125 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +324 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +324 -131
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +307 -114
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-RBWL4HRB.js.map +0 -1
- /package/dist/{chunk-NON5AXOR.js.map → chunk-RC3CIDZO.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -42,6 +42,15 @@ var init_cjs_shims = __esm({
|
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
// src/auth.ts
|
|
45
|
+
function isLoopbackHost(host) {
|
|
46
|
+
if (!host) return false;
|
|
47
|
+
return LOOPBACK_HOSTS.has(host);
|
|
48
|
+
}
|
|
49
|
+
function assertBindAuthority(host, token) {
|
|
50
|
+
if (token && token.length > 0) return;
|
|
51
|
+
if (isLoopbackHost(host)) return;
|
|
52
|
+
throw new BindAuthorityError(host);
|
|
53
|
+
}
|
|
45
54
|
function mountBearerAuth(app, opts) {
|
|
46
55
|
if (!opts.token || opts.token.length === 0) return;
|
|
47
56
|
if (opts.trustProxy) return;
|
|
@@ -73,12 +82,40 @@ function mountBearerAuth(app, opts) {
|
|
|
73
82
|
done();
|
|
74
83
|
});
|
|
75
84
|
}
|
|
76
|
-
|
|
85
|
+
function parseBoolEnv(v) {
|
|
86
|
+
if (!v) return false;
|
|
87
|
+
return v === "true" || v === "1";
|
|
88
|
+
}
|
|
89
|
+
function readAuthEnv(env = process.env) {
|
|
90
|
+
const t = env.NEAT_AUTH_TOKEN;
|
|
91
|
+
const ot = env.NEAT_OTEL_TOKEN;
|
|
92
|
+
return {
|
|
93
|
+
authToken: t && t.length > 0 ? t : void 0,
|
|
94
|
+
otelToken: ot && ot.length > 0 ? ot : t && t.length > 0 ? t : void 0,
|
|
95
|
+
trustProxy: env.NEAT_AUTH_PROXY === "true",
|
|
96
|
+
publicRead: parseBoolEnv(env.NEAT_PUBLIC_READ)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_SUFFIXES;
|
|
77
100
|
var init_auth = __esm({
|
|
78
101
|
"src/auth.ts"() {
|
|
79
102
|
"use strict";
|
|
80
103
|
init_cjs_shims();
|
|
81
104
|
import_node_crypto = require("crypto");
|
|
105
|
+
LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
106
|
+
"127.0.0.1",
|
|
107
|
+
"localhost",
|
|
108
|
+
"::1",
|
|
109
|
+
"::ffff:127.0.0.1"
|
|
110
|
+
]);
|
|
111
|
+
BindAuthorityError = class extends Error {
|
|
112
|
+
constructor(host) {
|
|
113
|
+
super(
|
|
114
|
+
`NEAT refuses to bind on a public interface without \`NEAT_AUTH_TOKEN\` set (host="${host}"). Set the token or bind to loopback only.`
|
|
115
|
+
);
|
|
116
|
+
this.name = "BindAuthorityError";
|
|
117
|
+
}
|
|
118
|
+
};
|
|
82
119
|
PUBLIC_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
83
120
|
DEFAULT_UNAUTH_SUFFIXES = [
|
|
84
121
|
"/health",
|
|
@@ -555,6 +592,7 @@ __export(cli_exports, {
|
|
|
555
592
|
CLAUDE_SKILL_CONFIG: () => CLAUDE_SKILL_CONFIG,
|
|
556
593
|
QUERY_VERBS: () => QUERY_VERBS,
|
|
557
594
|
parseArgs: () => parseArgs,
|
|
595
|
+
printBanner: () => printBanner,
|
|
558
596
|
readPackageVersion: () => readPackageVersion,
|
|
559
597
|
runInit: () => runInit,
|
|
560
598
|
runQueryVerb: () => runQueryVerb,
|
|
@@ -993,6 +1031,24 @@ function isFrontierNode(graph, nodeId) {
|
|
|
993
1031
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
994
1032
|
return attrs.type === import_types.NodeType.FrontierNode;
|
|
995
1033
|
}
|
|
1034
|
+
function resolveOwningService(graph, nodeId) {
|
|
1035
|
+
if (!graph.hasNode(nodeId)) return null;
|
|
1036
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
1037
|
+
if (attrs.type === import_types.NodeType.ServiceNode) {
|
|
1038
|
+
return { id: nodeId, svc: attrs };
|
|
1039
|
+
}
|
|
1040
|
+
if (attrs.type === import_types.NodeType.FileNode) {
|
|
1041
|
+
for (const edgeId of graph.inboundEdges(nodeId)) {
|
|
1042
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
1043
|
+
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
1044
|
+
const owner = graph.getNodeAttributes(e.source);
|
|
1045
|
+
if (owner.type === import_types.NodeType.ServiceNode) {
|
|
1046
|
+
return { id: e.source, svc: owner };
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
996
1052
|
function bestEdgeBySource(graph, edgeIds) {
|
|
997
1053
|
const best = /* @__PURE__ */ new Map();
|
|
998
1054
|
for (const id of edgeIds) {
|
|
@@ -1099,9 +1155,9 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1099
1155
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
1100
1156
|
if (candidatePairs.length === 0) return null;
|
|
1101
1157
|
for (const id of walk.path) {
|
|
1102
|
-
const
|
|
1103
|
-
if (
|
|
1104
|
-
const svc =
|
|
1158
|
+
const owner = resolveOwningService(graph, id);
|
|
1159
|
+
if (!owner) continue;
|
|
1160
|
+
const { id: serviceId3, svc } = owner;
|
|
1105
1161
|
const deps = svc.dependencies ?? {};
|
|
1106
1162
|
for (const pair of candidatePairs) {
|
|
1107
1163
|
const declared = deps[pair.driver];
|
|
@@ -1114,7 +1170,7 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1114
1170
|
);
|
|
1115
1171
|
if (!result.compatible) {
|
|
1116
1172
|
return {
|
|
1117
|
-
rootCauseNode:
|
|
1173
|
+
rootCauseNode: serviceId3,
|
|
1118
1174
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1119
1175
|
...result.minDriverVersion ? {
|
|
1120
1176
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1127,9 +1183,9 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1127
1183
|
}
|
|
1128
1184
|
function serviceRootCauseShape(graph, _origin, walk) {
|
|
1129
1185
|
for (const id of walk.path) {
|
|
1130
|
-
const
|
|
1131
|
-
if (
|
|
1132
|
-
const svc =
|
|
1186
|
+
const owner = resolveOwningService(graph, id);
|
|
1187
|
+
if (!owner) continue;
|
|
1188
|
+
const { id: serviceId3, svc } = owner;
|
|
1133
1189
|
const deps = svc.dependencies ?? {};
|
|
1134
1190
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1135
1191
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1138,7 +1194,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1138
1194
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1139
1195
|
if (!result.compatible && result.reason) {
|
|
1140
1196
|
return {
|
|
1141
|
-
rootCauseNode:
|
|
1197
|
+
rootCauseNode: serviceId3,
|
|
1142
1198
|
rootCauseReason: result.reason,
|
|
1143
1199
|
...result.requiredNodeVersion ? {
|
|
1144
1200
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1153,7 +1209,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1153
1209
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1154
1210
|
if (!result.compatible && result.reason) {
|
|
1155
1211
|
return {
|
|
1156
|
-
rootCauseNode:
|
|
1212
|
+
rootCauseNode: serviceId3,
|
|
1157
1213
|
rootCauseReason: result.reason,
|
|
1158
1214
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1159
1215
|
};
|
|
@@ -1162,9 +1218,15 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1162
1218
|
}
|
|
1163
1219
|
return null;
|
|
1164
1220
|
}
|
|
1221
|
+
function fileRootCauseShape(graph, origin, walk) {
|
|
1222
|
+
const owner = resolveOwningService(graph, origin.id);
|
|
1223
|
+
if (!owner) return null;
|
|
1224
|
+
return serviceRootCauseShape(graph, owner.svc, walk);
|
|
1225
|
+
}
|
|
1165
1226
|
var rootCauseShapes = {
|
|
1166
1227
|
[import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
1167
|
-
[import_types.NodeType.ServiceNode]: serviceRootCauseShape
|
|
1228
|
+
[import_types.NodeType.ServiceNode]: serviceRootCauseShape,
|
|
1229
|
+
[import_types.NodeType.FileNode]: fileRootCauseShape
|
|
1168
1230
|
};
|
|
1169
1231
|
function getRootCause(graph, errorNodeId, errorEvent) {
|
|
1170
1232
|
if (!graph.hasNode(errorNodeId)) return null;
|
|
@@ -1649,6 +1711,86 @@ function hostFromUrl(u) {
|
|
|
1649
1711
|
function pickAddress(span) {
|
|
1650
1712
|
return pickAttr(span, "server.address", "net.peer.name", "net.host.name") ?? hostFromUrl(pickAttr(span, "url.full", "http.url"));
|
|
1651
1713
|
}
|
|
1714
|
+
var CODE_FILEPATH_ATTR = "code.filepath";
|
|
1715
|
+
var CODE_LINENO_ATTR = "code.lineno";
|
|
1716
|
+
var CODE_FUNCTION_ATTR = "code.function";
|
|
1717
|
+
function toPosix(p) {
|
|
1718
|
+
return p.split("\\").join("/");
|
|
1719
|
+
}
|
|
1720
|
+
function languageForExt(relPath) {
|
|
1721
|
+
const dot = relPath.lastIndexOf(".");
|
|
1722
|
+
if (dot === -1) return void 0;
|
|
1723
|
+
switch (relPath.slice(dot).toLowerCase()) {
|
|
1724
|
+
case ".py":
|
|
1725
|
+
return "python";
|
|
1726
|
+
case ".ts":
|
|
1727
|
+
case ".tsx":
|
|
1728
|
+
return "typescript";
|
|
1729
|
+
case ".js":
|
|
1730
|
+
case ".jsx":
|
|
1731
|
+
case ".mjs":
|
|
1732
|
+
case ".cjs":
|
|
1733
|
+
return "javascript";
|
|
1734
|
+
default:
|
|
1735
|
+
return void 0;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
function relPathForRuntimeFile(filepath, serviceNode) {
|
|
1739
|
+
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1740
|
+
const root = serviceNode?.repoPath;
|
|
1741
|
+
if (root && root !== "." && root.length > 0) {
|
|
1742
|
+
const rootPosix = toPosix(root);
|
|
1743
|
+
const anchor = `/${rootPosix}/`;
|
|
1744
|
+
const idx = p.lastIndexOf(anchor);
|
|
1745
|
+
if (idx !== -1) return p.slice(idx + anchor.length);
|
|
1746
|
+
const base = rootPosix.split("/").filter(Boolean).pop();
|
|
1747
|
+
if (base) {
|
|
1748
|
+
const baseAnchor = `/${base}/`;
|
|
1749
|
+
const bidx = p.lastIndexOf(baseAnchor);
|
|
1750
|
+
if (bidx !== -1) return p.slice(bidx + baseAnchor.length);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
|
|
1754
|
+
return p.length > 0 ? p : null;
|
|
1755
|
+
}
|
|
1756
|
+
function callSiteFromSpan(span, serviceNode) {
|
|
1757
|
+
const filepath = span.attributes[CODE_FILEPATH_ATTR];
|
|
1758
|
+
if (typeof filepath !== "string" || filepath.length === 0) return null;
|
|
1759
|
+
const relPath = relPathForRuntimeFile(filepath, serviceNode);
|
|
1760
|
+
if (!relPath) return null;
|
|
1761
|
+
const linenoRaw = span.attributes[CODE_LINENO_ATTR];
|
|
1762
|
+
const line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
|
|
1763
|
+
const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
|
|
1764
|
+
const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
|
|
1765
|
+
return { relPath, ...line !== void 0 ? { line } : {}, ...fn ? { fn } : {} };
|
|
1766
|
+
}
|
|
1767
|
+
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
1768
|
+
const fileNodeId = (0, import_types3.fileId)(serviceName, callSite.relPath);
|
|
1769
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
1770
|
+
const language = languageForExt(callSite.relPath);
|
|
1771
|
+
const node = {
|
|
1772
|
+
id: fileNodeId,
|
|
1773
|
+
type: import_types3.NodeType.FileNode,
|
|
1774
|
+
service: serviceName,
|
|
1775
|
+
path: callSite.relPath,
|
|
1776
|
+
...language ? { language } : {},
|
|
1777
|
+
discoveredVia: "otel"
|
|
1778
|
+
};
|
|
1779
|
+
graph.addNode(fileNodeId, node);
|
|
1780
|
+
}
|
|
1781
|
+
const containsId = makeObservedEdgeId(import_types3.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
|
|
1782
|
+
if (!graph.hasEdge(containsId)) {
|
|
1783
|
+
const edge = {
|
|
1784
|
+
id: containsId,
|
|
1785
|
+
source: serviceNodeId,
|
|
1786
|
+
target: fileNodeId,
|
|
1787
|
+
type: import_types3.EdgeType.CONTAINS,
|
|
1788
|
+
provenance: import_types3.Provenance.OBSERVED
|
|
1789
|
+
};
|
|
1790
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
1791
|
+
}
|
|
1792
|
+
return fileNodeId;
|
|
1793
|
+
}
|
|
1652
1794
|
function makeObservedEdgeId(type, source, target) {
|
|
1653
1795
|
return (0, import_types3.observedEdgeId)(source, target, type);
|
|
1654
1796
|
}
|
|
@@ -1891,6 +2033,9 @@ async function handleSpan(ctx, span) {
|
|
|
1891
2033
|
const sourceId = ensureServiceNode(ctx.graph, span.service, env);
|
|
1892
2034
|
const isError = span.statusCode === 2;
|
|
1893
2035
|
cacheSpanService(span, nowMs);
|
|
2036
|
+
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2037
|
+
const callSite = callSiteFromSpan(span, sourceServiceNode);
|
|
2038
|
+
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
1894
2039
|
let affectedNode = sourceId;
|
|
1895
2040
|
if (span.dbSystem) {
|
|
1896
2041
|
const host = pickAddress(span);
|
|
@@ -1900,7 +2045,7 @@ async function handleSpan(ctx, span) {
|
|
|
1900
2045
|
const result = upsertObservedEdge(
|
|
1901
2046
|
ctx.graph,
|
|
1902
2047
|
import_types3.EdgeType.CONNECTS_TO,
|
|
1903
|
-
|
|
2048
|
+
observedSource(),
|
|
1904
2049
|
targetId,
|
|
1905
2050
|
ts,
|
|
1906
2051
|
isError
|
|
@@ -1916,7 +2061,7 @@ async function handleSpan(ctx, span) {
|
|
|
1916
2061
|
upsertObservedEdge(
|
|
1917
2062
|
ctx.graph,
|
|
1918
2063
|
import_types3.EdgeType.CALLS,
|
|
1919
|
-
|
|
2064
|
+
observedSource(),
|
|
1920
2065
|
targetId,
|
|
1921
2066
|
ts,
|
|
1922
2067
|
isError
|
|
@@ -1928,7 +2073,7 @@ async function handleSpan(ctx, span) {
|
|
|
1928
2073
|
upsertObservedEdge(
|
|
1929
2074
|
ctx.graph,
|
|
1930
2075
|
import_types3.EdgeType.CALLS,
|
|
1931
|
-
|
|
2076
|
+
observedSource(),
|
|
1932
2077
|
frontierNodeId,
|
|
1933
2078
|
ts,
|
|
1934
2079
|
isError
|
|
@@ -3571,7 +3716,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3571
3716
|
|
|
3572
3717
|
// src/extract/calls/index.ts
|
|
3573
3718
|
init_cjs_shims();
|
|
3574
|
-
var
|
|
3719
|
+
var import_types15 = require("@neat.is/types");
|
|
3575
3720
|
|
|
3576
3721
|
// src/extract/calls/http.ts
|
|
3577
3722
|
init_cjs_shims();
|
|
@@ -3579,12 +3724,13 @@ var import_node_path20 = __toESM(require("path"), 1);
|
|
|
3579
3724
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3580
3725
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3581
3726
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3582
|
-
var
|
|
3727
|
+
var import_types10 = require("@neat.is/types");
|
|
3583
3728
|
|
|
3584
3729
|
// src/extract/calls/shared.ts
|
|
3585
3730
|
init_cjs_shims();
|
|
3586
3731
|
var import_node_fs13 = require("fs");
|
|
3587
3732
|
var import_node_path19 = __toESM(require("path"), 1);
|
|
3733
|
+
var import_types9 = require("@neat.is/types");
|
|
3588
3734
|
async function walkSourceFiles(dir) {
|
|
3589
3735
|
const out = [];
|
|
3590
3736
|
async function walk(current) {
|
|
@@ -3624,6 +3770,58 @@ function snippet(text, line) {
|
|
|
3624
3770
|
const lines = text.split("\n");
|
|
3625
3771
|
return (lines[line - 1] ?? "").trim();
|
|
3626
3772
|
}
|
|
3773
|
+
function toPosix2(p) {
|
|
3774
|
+
return p.split("\\").join("/");
|
|
3775
|
+
}
|
|
3776
|
+
function languageForPath(relPath) {
|
|
3777
|
+
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3778
|
+
case ".py":
|
|
3779
|
+
return "python";
|
|
3780
|
+
case ".ts":
|
|
3781
|
+
case ".tsx":
|
|
3782
|
+
return "typescript";
|
|
3783
|
+
case ".js":
|
|
3784
|
+
case ".jsx":
|
|
3785
|
+
case ".mjs":
|
|
3786
|
+
case ".cjs":
|
|
3787
|
+
return "javascript";
|
|
3788
|
+
default:
|
|
3789
|
+
return void 0;
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3793
|
+
let nodesAdded = 0;
|
|
3794
|
+
let edgesAdded = 0;
|
|
3795
|
+
const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
|
|
3796
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3797
|
+
const language = languageForPath(relPath);
|
|
3798
|
+
const node = {
|
|
3799
|
+
id: fileNodeId,
|
|
3800
|
+
type: import_types9.NodeType.FileNode,
|
|
3801
|
+
service: serviceName,
|
|
3802
|
+
path: relPath,
|
|
3803
|
+
...language ? { language } : {},
|
|
3804
|
+
discoveredVia: "static"
|
|
3805
|
+
};
|
|
3806
|
+
graph.addNode(fileNodeId, node);
|
|
3807
|
+
nodesAdded++;
|
|
3808
|
+
}
|
|
3809
|
+
const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
|
|
3810
|
+
if (!graph.hasEdge(containsId)) {
|
|
3811
|
+
const edge = {
|
|
3812
|
+
id: containsId,
|
|
3813
|
+
source: serviceNodeId,
|
|
3814
|
+
target: fileNodeId,
|
|
3815
|
+
type: import_types9.EdgeType.CONTAINS,
|
|
3816
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3817
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3818
|
+
evidence: { file: relPath }
|
|
3819
|
+
};
|
|
3820
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3821
|
+
edgesAdded++;
|
|
3822
|
+
}
|
|
3823
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3824
|
+
}
|
|
3627
3825
|
|
|
3628
3826
|
// src/extract/calls/http.ts
|
|
3629
3827
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
@@ -3657,16 +3855,16 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3657
3855
|
const tree = parser.parse(source);
|
|
3658
3856
|
const literals = [];
|
|
3659
3857
|
collectStringLiterals(tree.rootNode, literals);
|
|
3660
|
-
const
|
|
3858
|
+
const out = [];
|
|
3661
3859
|
for (const lit of literals) {
|
|
3662
3860
|
if (isInsideJsxExternalLink(lit.node)) continue;
|
|
3663
3861
|
for (const host of knownHosts) {
|
|
3664
3862
|
if (urlMatchesHost(lit.text, host)) {
|
|
3665
|
-
|
|
3863
|
+
out.push({ host, line: lit.node.startPosition.row + 1 });
|
|
3666
3864
|
}
|
|
3667
3865
|
}
|
|
3668
3866
|
}
|
|
3669
|
-
return
|
|
3867
|
+
return out;
|
|
3670
3868
|
}
|
|
3671
3869
|
function makeJsParser() {
|
|
3672
3870
|
const p = new import_tree_sitter.default();
|
|
@@ -3689,71 +3887,78 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3689
3887
|
hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
|
|
3690
3888
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3691
3889
|
}
|
|
3890
|
+
let nodesAdded = 0;
|
|
3692
3891
|
let edgesAdded = 0;
|
|
3693
3892
|
for (const service of services) {
|
|
3694
3893
|
const files = await loadSourceFiles(service.dir);
|
|
3695
|
-
const
|
|
3894
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3696
3895
|
for (const file of files) {
|
|
3697
3896
|
if (isTestPath(file.path)) continue;
|
|
3698
3897
|
const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3699
|
-
let
|
|
3898
|
+
let sites;
|
|
3700
3899
|
try {
|
|
3701
|
-
|
|
3900
|
+
sites = callsFromSource(file.content, parser, knownHosts);
|
|
3702
3901
|
} catch (err) {
|
|
3703
3902
|
recordExtractionError("http call extraction", file.path, err);
|
|
3704
3903
|
continue;
|
|
3705
3904
|
}
|
|
3706
|
-
|
|
3707
|
-
|
|
3905
|
+
if (sites.length === 0) continue;
|
|
3906
|
+
const relFile = toPosix2(import_node_path20.default.relative(service.dir, file.path));
|
|
3907
|
+
for (const site of sites) {
|
|
3908
|
+
const targetId = hostToNodeId.get(site.host);
|
|
3708
3909
|
if (!targetId || targetId === service.node.id) continue;
|
|
3709
|
-
|
|
3710
|
-
|
|
3910
|
+
const dedupKey = `${relFile}|${targetId}`;
|
|
3911
|
+
if (seen.has(dedupKey)) continue;
|
|
3912
|
+
seen.add(dedupKey);
|
|
3913
|
+
const confidence = (0, import_types10.confidenceForExtracted)("hostname-shape-match");
|
|
3914
|
+
const ev = {
|
|
3915
|
+
file: relFile,
|
|
3916
|
+
line: site.line,
|
|
3917
|
+
snippet: snippet(file.content, site.line)
|
|
3918
|
+
};
|
|
3919
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
3920
|
+
graph,
|
|
3921
|
+
service.pkg.name,
|
|
3922
|
+
service.node.id,
|
|
3923
|
+
relFile
|
|
3924
|
+
);
|
|
3925
|
+
nodesAdded += n;
|
|
3926
|
+
edgesAdded += e;
|
|
3927
|
+
if (!(0, import_types10.passesExtractedFloor)(confidence)) {
|
|
3928
|
+
noteExtractedDropped({
|
|
3929
|
+
source: fileNodeId,
|
|
3930
|
+
target: targetId,
|
|
3931
|
+
type: import_types10.EdgeType.CALLS,
|
|
3932
|
+
confidence,
|
|
3933
|
+
confidenceKind: "hostname-shape-match",
|
|
3934
|
+
evidence: ev
|
|
3935
|
+
});
|
|
3936
|
+
continue;
|
|
3937
|
+
}
|
|
3938
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types10.EdgeType.CALLS);
|
|
3939
|
+
if (!graph.hasEdge(edgeId)) {
|
|
3940
|
+
const edge = {
|
|
3941
|
+
id: edgeId,
|
|
3942
|
+
source: fileNodeId,
|
|
3943
|
+
target: targetId,
|
|
3944
|
+
type: import_types10.EdgeType.CALLS,
|
|
3945
|
+
provenance: import_types10.Provenance.EXTRACTED,
|
|
3946
|
+
confidence,
|
|
3947
|
+
evidence: ev
|
|
3948
|
+
};
|
|
3949
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, targetId, edge);
|
|
3950
|
+
edgesAdded++;
|
|
3711
3951
|
}
|
|
3712
|
-
}
|
|
3713
|
-
}
|
|
3714
|
-
for (const [targetId, evidenceFile] of seenTargets) {
|
|
3715
|
-
const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
|
|
3716
|
-
const line = lineOf(fileContent, `//${evidenceFile.host}`);
|
|
3717
|
-
const confidence = (0, import_types9.confidenceForExtracted)("hostname-shape-match");
|
|
3718
|
-
const ev = {
|
|
3719
|
-
file: import_node_path20.default.relative(service.dir, evidenceFile.file),
|
|
3720
|
-
line,
|
|
3721
|
-
snippet: snippet(fileContent, line)
|
|
3722
|
-
};
|
|
3723
|
-
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, targetId, import_types9.EdgeType.CALLS);
|
|
3724
|
-
if (!(0, import_types9.passesExtractedFloor)(confidence)) {
|
|
3725
|
-
noteExtractedDropped({
|
|
3726
|
-
source: service.node.id,
|
|
3727
|
-
target: targetId,
|
|
3728
|
-
type: import_types9.EdgeType.CALLS,
|
|
3729
|
-
confidence,
|
|
3730
|
-
confidenceKind: "hostname-shape-match",
|
|
3731
|
-
evidence: ev
|
|
3732
|
-
});
|
|
3733
|
-
continue;
|
|
3734
|
-
}
|
|
3735
|
-
const edge = {
|
|
3736
|
-
id: edgeId,
|
|
3737
|
-
source: service.node.id,
|
|
3738
|
-
target: targetId,
|
|
3739
|
-
type: import_types9.EdgeType.CALLS,
|
|
3740
|
-
provenance: import_types9.Provenance.EXTRACTED,
|
|
3741
|
-
confidence,
|
|
3742
|
-
evidence: ev
|
|
3743
|
-
};
|
|
3744
|
-
if (!graph.hasEdge(edge.id)) {
|
|
3745
|
-
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
3746
|
-
edgesAdded++;
|
|
3747
3952
|
}
|
|
3748
3953
|
}
|
|
3749
3954
|
}
|
|
3750
|
-
return edgesAdded;
|
|
3955
|
+
return { nodesAdded, edgesAdded };
|
|
3751
3956
|
}
|
|
3752
3957
|
|
|
3753
3958
|
// src/extract/calls/kafka.ts
|
|
3754
3959
|
init_cjs_shims();
|
|
3755
3960
|
var import_node_path21 = __toESM(require("path"), 1);
|
|
3756
|
-
var
|
|
3961
|
+
var import_types11 = require("@neat.is/types");
|
|
3757
3962
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3758
3963
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
3759
3964
|
function findAll(re, text) {
|
|
@@ -3774,7 +3979,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3774
3979
|
seen.add(key);
|
|
3775
3980
|
const line = lineOf(file.content, topic);
|
|
3776
3981
|
out.push({
|
|
3777
|
-
infraId: (0,
|
|
3982
|
+
infraId: (0, import_types11.infraId)("kafka-topic", topic),
|
|
3778
3983
|
name: topic,
|
|
3779
3984
|
kind: "kafka-topic",
|
|
3780
3985
|
edgeType,
|
|
@@ -3797,7 +4002,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3797
4002
|
// src/extract/calls/redis.ts
|
|
3798
4003
|
init_cjs_shims();
|
|
3799
4004
|
var import_node_path22 = __toESM(require("path"), 1);
|
|
3800
|
-
var
|
|
4005
|
+
var import_types12 = require("@neat.is/types");
|
|
3801
4006
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3802
4007
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
3803
4008
|
const out = [];
|
|
@@ -3810,7 +4015,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3810
4015
|
seen.add(host);
|
|
3811
4016
|
const line = lineOf(file.content, host);
|
|
3812
4017
|
out.push({
|
|
3813
|
-
infraId: (0,
|
|
4018
|
+
infraId: (0, import_types12.infraId)("redis", host),
|
|
3814
4019
|
name: host,
|
|
3815
4020
|
kind: "redis",
|
|
3816
4021
|
edgeType: "CALLS",
|
|
@@ -3831,7 +4036,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3831
4036
|
// src/extract/calls/aws.ts
|
|
3832
4037
|
init_cjs_shims();
|
|
3833
4038
|
var import_node_path23 = __toESM(require("path"), 1);
|
|
3834
|
-
var
|
|
4039
|
+
var import_types13 = require("@neat.is/types");
|
|
3835
4040
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3836
4041
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3837
4042
|
function hasMarker(text, markers) {
|
|
@@ -3855,7 +4060,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3855
4060
|
seen.add(key);
|
|
3856
4061
|
const line = lineOf(file.content, name);
|
|
3857
4062
|
out.push({
|
|
3858
|
-
infraId: (0,
|
|
4063
|
+
infraId: (0, import_types13.infraId)(kind, name),
|
|
3859
4064
|
name,
|
|
3860
4065
|
kind,
|
|
3861
4066
|
edgeType: "CALLS",
|
|
@@ -3890,7 +4095,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3890
4095
|
// src/extract/calls/grpc.ts
|
|
3891
4096
|
init_cjs_shims();
|
|
3892
4097
|
var import_node_path24 = __toESM(require("path"), 1);
|
|
3893
|
-
var
|
|
4098
|
+
var import_types14 = require("@neat.is/types");
|
|
3894
4099
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
3895
4100
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
3896
4101
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -3938,7 +4143,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3938
4143
|
const { kind } = classified;
|
|
3939
4144
|
const line = lineOf(file.content, m[0]);
|
|
3940
4145
|
out.push({
|
|
3941
|
-
infraId: (0,
|
|
4146
|
+
infraId: (0, import_types14.infraId)(kind, name),
|
|
3942
4147
|
name,
|
|
3943
4148
|
kind,
|
|
3944
4149
|
edgeType: "CALLS",
|
|
@@ -3960,11 +4165,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3960
4165
|
function edgeTypeFromEndpoint(ep) {
|
|
3961
4166
|
switch (ep.edgeType) {
|
|
3962
4167
|
case "PUBLISHES_TO":
|
|
3963
|
-
return
|
|
4168
|
+
return import_types15.EdgeType.PUBLISHES_TO;
|
|
3964
4169
|
case "CONSUMES_FROM":
|
|
3965
|
-
return
|
|
4170
|
+
return import_types15.EdgeType.CONSUMES_FROM;
|
|
3966
4171
|
default:
|
|
3967
|
-
return
|
|
4172
|
+
return import_types15.EdgeType.CALLS;
|
|
3968
4173
|
}
|
|
3969
4174
|
}
|
|
3970
4175
|
function isAwsKind(kind) {
|
|
@@ -3991,7 +4196,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3991
4196
|
if (!graph.hasNode(ep.infraId)) {
|
|
3992
4197
|
const node = {
|
|
3993
4198
|
id: ep.infraId,
|
|
3994
|
-
type:
|
|
4199
|
+
type: import_types15.NodeType.InfraNode,
|
|
3995
4200
|
name: ep.name,
|
|
3996
4201
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
3997
4202
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -4003,13 +4208,19 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4003
4208
|
nodesAdded++;
|
|
4004
4209
|
}
|
|
4005
4210
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
4006
|
-
const
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4211
|
+
const confidence = (0, import_types15.confidenceForExtracted)(ep.confidenceKind);
|
|
4212
|
+
const relFile = toPosix2(ep.evidence.file);
|
|
4213
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
4214
|
+
graph,
|
|
4215
|
+
service.pkg.name,
|
|
4216
|
+
service.node.id,
|
|
4217
|
+
relFile
|
|
4218
|
+
);
|
|
4219
|
+
nodesAdded += n;
|
|
4220
|
+
edgesAdded += e;
|
|
4221
|
+
if (!(0, import_types15.passesExtractedFloor)(confidence)) {
|
|
4011
4222
|
noteExtractedDropped({
|
|
4012
|
-
source:
|
|
4223
|
+
source: fileNodeId,
|
|
4013
4224
|
target: ep.infraId,
|
|
4014
4225
|
type: edgeType,
|
|
4015
4226
|
confidence,
|
|
@@ -4018,13 +4229,16 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4018
4229
|
});
|
|
4019
4230
|
continue;
|
|
4020
4231
|
}
|
|
4232
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, ep.infraId, edgeType);
|
|
4233
|
+
if (seenEdges.has(edgeId)) continue;
|
|
4234
|
+
seenEdges.add(edgeId);
|
|
4021
4235
|
if (!graph.hasEdge(edgeId)) {
|
|
4022
4236
|
const edge = {
|
|
4023
4237
|
id: edgeId,
|
|
4024
|
-
source:
|
|
4238
|
+
source: fileNodeId,
|
|
4025
4239
|
target: ep.infraId,
|
|
4026
4240
|
type: edgeType,
|
|
4027
|
-
provenance:
|
|
4241
|
+
provenance: import_types15.Provenance.EXTRACTED,
|
|
4028
4242
|
confidence,
|
|
4029
4243
|
evidence: ep.evidence
|
|
4030
4244
|
};
|
|
@@ -4036,11 +4250,11 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4036
4250
|
return { nodesAdded, edgesAdded };
|
|
4037
4251
|
}
|
|
4038
4252
|
async function addCallEdges(graph, services) {
|
|
4039
|
-
const
|
|
4253
|
+
const http2 = await addHttpCallEdges(graph, services);
|
|
4040
4254
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
4041
4255
|
return {
|
|
4042
|
-
nodesAdded: ext.nodesAdded,
|
|
4043
|
-
edgesAdded:
|
|
4256
|
+
nodesAdded: http2.nodesAdded + ext.nodesAdded,
|
|
4257
|
+
edgesAdded: http2.edgesAdded + ext.edgesAdded
|
|
4044
4258
|
};
|
|
4045
4259
|
}
|
|
4046
4260
|
|
|
@@ -4050,15 +4264,15 @@ init_cjs_shims();
|
|
|
4050
4264
|
// src/extract/infra/docker-compose.ts
|
|
4051
4265
|
init_cjs_shims();
|
|
4052
4266
|
var import_node_path25 = __toESM(require("path"), 1);
|
|
4053
|
-
var
|
|
4267
|
+
var import_types17 = require("@neat.is/types");
|
|
4054
4268
|
|
|
4055
4269
|
// src/extract/infra/shared.ts
|
|
4056
4270
|
init_cjs_shims();
|
|
4057
|
-
var
|
|
4271
|
+
var import_types16 = require("@neat.is/types");
|
|
4058
4272
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
4059
4273
|
return {
|
|
4060
|
-
id: (0,
|
|
4061
|
-
type:
|
|
4274
|
+
id: (0, import_types16.infraId)(kind, name),
|
|
4275
|
+
type: import_types16.NodeType.InfraNode,
|
|
4062
4276
|
name,
|
|
4063
4277
|
provider,
|
|
4064
4278
|
kind,
|
|
@@ -4137,15 +4351,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4137
4351
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
4138
4352
|
const targetId = composeNameToNodeId.get(dep);
|
|
4139
4353
|
if (!targetId) continue;
|
|
4140
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
4354
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types17.EdgeType.DEPENDS_ON);
|
|
4141
4355
|
if (graph.hasEdge(edgeId)) continue;
|
|
4142
4356
|
const edge = {
|
|
4143
4357
|
id: edgeId,
|
|
4144
4358
|
source: sourceId,
|
|
4145
4359
|
target: targetId,
|
|
4146
|
-
type:
|
|
4147
|
-
provenance:
|
|
4148
|
-
confidence: (0,
|
|
4360
|
+
type: import_types17.EdgeType.DEPENDS_ON,
|
|
4361
|
+
provenance: import_types17.Provenance.EXTRACTED,
|
|
4362
|
+
confidence: (0, import_types17.confidenceForExtracted)("structural"),
|
|
4149
4363
|
evidence: { file: evidenceFile }
|
|
4150
4364
|
};
|
|
4151
4365
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4159,7 +4373,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4159
4373
|
init_cjs_shims();
|
|
4160
4374
|
var import_node_path26 = __toESM(require("path"), 1);
|
|
4161
4375
|
var import_node_fs14 = require("fs");
|
|
4162
|
-
var
|
|
4376
|
+
var import_types18 = require("@neat.is/types");
|
|
4163
4377
|
function runtimeImage(content) {
|
|
4164
4378
|
const lines = content.split("\n");
|
|
4165
4379
|
let last = null;
|
|
@@ -4198,15 +4412,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4198
4412
|
graph.addNode(node.id, node);
|
|
4199
4413
|
nodesAdded++;
|
|
4200
4414
|
}
|
|
4201
|
-
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, node.id,
|
|
4415
|
+
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, node.id, import_types18.EdgeType.RUNS_ON);
|
|
4202
4416
|
if (!graph.hasEdge(edgeId)) {
|
|
4203
4417
|
const edge = {
|
|
4204
4418
|
id: edgeId,
|
|
4205
4419
|
source: service.node.id,
|
|
4206
4420
|
target: node.id,
|
|
4207
|
-
type:
|
|
4208
|
-
provenance:
|
|
4209
|
-
confidence: (0,
|
|
4421
|
+
type: import_types18.EdgeType.RUNS_ON,
|
|
4422
|
+
provenance: import_types18.Provenance.EXTRACTED,
|
|
4423
|
+
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4210
4424
|
evidence: {
|
|
4211
4425
|
file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
|
|
4212
4426
|
}
|
|
@@ -4334,17 +4548,29 @@ var import_node_path30 = __toESM(require("path"), 1);
|
|
|
4334
4548
|
init_cjs_shims();
|
|
4335
4549
|
var import_node_fs17 = require("fs");
|
|
4336
4550
|
var import_node_path29 = __toESM(require("path"), 1);
|
|
4337
|
-
var
|
|
4551
|
+
var import_types19 = require("@neat.is/types");
|
|
4552
|
+
function dropOrphanedFileNodes(graph) {
|
|
4553
|
+
const orphans = [];
|
|
4554
|
+
graph.forEachNode((id, attrs) => {
|
|
4555
|
+
if (attrs.type !== import_types19.NodeType.FileNode) return;
|
|
4556
|
+
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
4557
|
+
orphans.push(id);
|
|
4558
|
+
}
|
|
4559
|
+
});
|
|
4560
|
+
for (const id of orphans) graph.dropNode(id);
|
|
4561
|
+
return orphans.length;
|
|
4562
|
+
}
|
|
4338
4563
|
function retireEdgesByFile(graph, file) {
|
|
4339
4564
|
const normalized = file.split("\\").join("/");
|
|
4340
4565
|
const toDrop = [];
|
|
4341
4566
|
graph.forEachEdge((id, attrs) => {
|
|
4342
4567
|
const edge = attrs;
|
|
4343
|
-
if (edge.provenance !==
|
|
4568
|
+
if (edge.provenance !== import_types19.Provenance.EXTRACTED) return;
|
|
4344
4569
|
if (!edge.evidence?.file) return;
|
|
4345
4570
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
4346
4571
|
});
|
|
4347
4572
|
for (const id of toDrop) graph.dropEdge(id);
|
|
4573
|
+
dropOrphanedFileNodes(graph);
|
|
4348
4574
|
return toDrop.length;
|
|
4349
4575
|
}
|
|
4350
4576
|
function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
@@ -4352,7 +4578,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4352
4578
|
const bases = [scanPath, ...serviceDirs];
|
|
4353
4579
|
graph.forEachEdge((id, attrs) => {
|
|
4354
4580
|
const edge = attrs;
|
|
4355
|
-
if (edge.provenance !==
|
|
4581
|
+
if (edge.provenance !== import_types19.Provenance.EXTRACTED) return;
|
|
4356
4582
|
const evidenceFile = edge.evidence?.file;
|
|
4357
4583
|
if (!evidenceFile) return;
|
|
4358
4584
|
if (import_node_path29.default.isAbsolute(evidenceFile)) {
|
|
@@ -4363,6 +4589,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4363
4589
|
if (!found) toDrop.push(id);
|
|
4364
4590
|
});
|
|
4365
4591
|
for (const id of toDrop) graph.dropEdge(id);
|
|
4592
|
+
dropOrphanedFileNodes(graph);
|
|
4366
4593
|
return toDrop.length;
|
|
4367
4594
|
}
|
|
4368
4595
|
|
|
@@ -4430,7 +4657,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4430
4657
|
|
|
4431
4658
|
// src/divergences.ts
|
|
4432
4659
|
init_cjs_shims();
|
|
4433
|
-
var
|
|
4660
|
+
var import_types20 = require("@neat.is/types");
|
|
4434
4661
|
function bucketKey(source, target, type) {
|
|
4435
4662
|
return `${type}|${source}|${target}`;
|
|
4436
4663
|
}
|
|
@@ -4438,22 +4665,22 @@ function bucketEdges(graph) {
|
|
|
4438
4665
|
const buckets = /* @__PURE__ */ new Map();
|
|
4439
4666
|
graph.forEachEdge((id, attrs) => {
|
|
4440
4667
|
const e = attrs;
|
|
4441
|
-
const parsed = (0,
|
|
4668
|
+
const parsed = (0, import_types20.parseEdgeId)(id);
|
|
4442
4669
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
4443
4670
|
const key = bucketKey(e.source, e.target, e.type);
|
|
4444
4671
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
4445
4672
|
switch (provenance) {
|
|
4446
|
-
case
|
|
4673
|
+
case import_types20.Provenance.EXTRACTED:
|
|
4447
4674
|
cur.extracted = e;
|
|
4448
4675
|
break;
|
|
4449
|
-
case
|
|
4676
|
+
case import_types20.Provenance.OBSERVED:
|
|
4450
4677
|
cur.observed = e;
|
|
4451
4678
|
break;
|
|
4452
|
-
case
|
|
4679
|
+
case import_types20.Provenance.INFERRED:
|
|
4453
4680
|
cur.inferred = e;
|
|
4454
4681
|
break;
|
|
4455
4682
|
default:
|
|
4456
|
-
if (e.provenance ===
|
|
4683
|
+
if (e.provenance === import_types20.Provenance.STALE) cur.stale = e;
|
|
4457
4684
|
}
|
|
4458
4685
|
buckets.set(key, cur);
|
|
4459
4686
|
});
|
|
@@ -4462,7 +4689,7 @@ function bucketEdges(graph) {
|
|
|
4462
4689
|
function nodeIsFrontier(graph, nodeId) {
|
|
4463
4690
|
if (!graph.hasNode(nodeId)) return false;
|
|
4464
4691
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4465
|
-
return attrs.type ===
|
|
4692
|
+
return attrs.type === import_types20.NodeType.FrontierNode;
|
|
4466
4693
|
}
|
|
4467
4694
|
function clampConfidence(n) {
|
|
4468
4695
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -4483,6 +4710,7 @@ function gradedConfidence(edge) {
|
|
|
4483
4710
|
}
|
|
4484
4711
|
function detectMissingDivergences(graph, bucket) {
|
|
4485
4712
|
const out = [];
|
|
4713
|
+
if (bucket.type === import_types20.EdgeType.CONTAINS) return out;
|
|
4486
4714
|
if (bucket.extracted && !bucket.observed) {
|
|
4487
4715
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
4488
4716
|
out.push({
|
|
@@ -4523,7 +4751,7 @@ function declaredHostFor(svc) {
|
|
|
4523
4751
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4524
4752
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4525
4753
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4526
|
-
if (e.type ===
|
|
4754
|
+
if (e.type === import_types20.EdgeType.CONFIGURED_BY && e.provenance === import_types20.Provenance.EXTRACTED) {
|
|
4527
4755
|
return true;
|
|
4528
4756
|
}
|
|
4529
4757
|
}
|
|
@@ -4536,10 +4764,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4536
4764
|
const out = [];
|
|
4537
4765
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4538
4766
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4539
|
-
if (edge.type !==
|
|
4540
|
-
if (edge.provenance !==
|
|
4767
|
+
if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
|
|
4768
|
+
if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
|
|
4541
4769
|
const target = graph.getNodeAttributes(edge.target);
|
|
4542
|
-
if (target.type !==
|
|
4770
|
+
if (target.type !== import_types20.NodeType.DatabaseNode) continue;
|
|
4543
4771
|
const observedHost = target.host?.trim();
|
|
4544
4772
|
if (!observedHost) continue;
|
|
4545
4773
|
if (observedHost === declaredHost) continue;
|
|
@@ -4561,10 +4789,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4561
4789
|
const deps = svc.dependencies ?? {};
|
|
4562
4790
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4563
4791
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4564
|
-
if (edge.type !==
|
|
4565
|
-
if (edge.provenance !==
|
|
4792
|
+
if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
|
|
4793
|
+
if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
|
|
4566
4794
|
const target = graph.getNodeAttributes(edge.target);
|
|
4567
|
-
if (target.type !==
|
|
4795
|
+
if (target.type !== import_types20.NodeType.DatabaseNode) continue;
|
|
4568
4796
|
for (const pair of compatPairs()) {
|
|
4569
4797
|
if (pair.engine !== target.engine) continue;
|
|
4570
4798
|
const declared = deps[pair.driver];
|
|
@@ -4625,7 +4853,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4625
4853
|
}
|
|
4626
4854
|
graph.forEachNode((nodeId, attrs) => {
|
|
4627
4855
|
const n = attrs;
|
|
4628
|
-
if (n.type !==
|
|
4856
|
+
if (n.type !== import_types20.NodeType.ServiceNode) return;
|
|
4629
4857
|
const svc = n;
|
|
4630
4858
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4631
4859
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -4658,7 +4886,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4658
4886
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4659
4887
|
return a.target.localeCompare(b.target);
|
|
4660
4888
|
});
|
|
4661
|
-
return
|
|
4889
|
+
return import_types20.DivergenceResultSchema.parse({
|
|
4662
4890
|
divergences: filtered,
|
|
4663
4891
|
totalAffected: filtered.length,
|
|
4664
4892
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4669,7 +4897,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4669
4897
|
init_cjs_shims();
|
|
4670
4898
|
var import_node_fs18 = require("fs");
|
|
4671
4899
|
var import_node_path31 = __toESM(require("path"), 1);
|
|
4672
|
-
var
|
|
4900
|
+
var import_types21 = require("@neat.is/types");
|
|
4673
4901
|
var SCHEMA_VERSION = 4;
|
|
4674
4902
|
function migrateV1ToV2(payload) {
|
|
4675
4903
|
const nodes = payload.graph.nodes;
|
|
@@ -4691,12 +4919,12 @@ function migrateV2ToV3(payload) {
|
|
|
4691
4919
|
for (const edge of edges) {
|
|
4692
4920
|
const attrs = edge.attributes;
|
|
4693
4921
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
4694
|
-
attrs.provenance =
|
|
4922
|
+
attrs.provenance = import_types21.Provenance.OBSERVED;
|
|
4695
4923
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
4696
4924
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
4697
4925
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
4698
4926
|
if (type && source && target) {
|
|
4699
|
-
const newId = (0,
|
|
4927
|
+
const newId = (0, import_types21.observedEdgeId)(source, target, type);
|
|
4700
4928
|
attrs.id = newId;
|
|
4701
4929
|
if (edge.key) edge.key = newId;
|
|
4702
4930
|
}
|
|
@@ -4816,7 +5044,7 @@ ${NEAT_OUT_LINE}
|
|
|
4816
5044
|
|
|
4817
5045
|
// src/summary.ts
|
|
4818
5046
|
init_cjs_shims();
|
|
4819
|
-
var
|
|
5047
|
+
var import_types22 = require("@neat.is/types");
|
|
4820
5048
|
function renderOtelEnvBlock() {
|
|
4821
5049
|
return [
|
|
4822
5050
|
"for prod OTel routing, set these in your deploy platform's env:",
|
|
@@ -4826,19 +5054,19 @@ function renderOtelEnvBlock() {
|
|
|
4826
5054
|
}
|
|
4827
5055
|
function findIncompatServices(nodes) {
|
|
4828
5056
|
return nodes.filter(
|
|
4829
|
-
(n) => n.type ===
|
|
5057
|
+
(n) => n.type === import_types22.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
|
|
4830
5058
|
);
|
|
4831
5059
|
}
|
|
4832
5060
|
function servicesWithoutObserved(nodes, edges) {
|
|
4833
5061
|
const seen = /* @__PURE__ */ new Set();
|
|
4834
5062
|
for (const e of edges) {
|
|
4835
|
-
if (e.provenance ===
|
|
5063
|
+
if (e.provenance === import_types22.Provenance.OBSERVED) {
|
|
4836
5064
|
seen.add(e.source);
|
|
4837
5065
|
seen.add(e.target);
|
|
4838
5066
|
}
|
|
4839
5067
|
}
|
|
4840
5068
|
return nodes.filter(
|
|
4841
|
-
(n) => n.type ===
|
|
5069
|
+
(n) => n.type === import_types22.NodeType.ServiceNode && !seen.has(n.id)
|
|
4842
5070
|
);
|
|
4843
5071
|
}
|
|
4844
5072
|
function formatDivergence(d) {
|
|
@@ -4920,7 +5148,7 @@ var import_chokidar = __toESM(require("chokidar"), 1);
|
|
|
4920
5148
|
init_cjs_shims();
|
|
4921
5149
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
4922
5150
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4923
|
-
var
|
|
5151
|
+
var import_types24 = require("@neat.is/types");
|
|
4924
5152
|
|
|
4925
5153
|
// src/diff.ts
|
|
4926
5154
|
init_cjs_shims();
|
|
@@ -5056,7 +5284,7 @@ init_cjs_shims();
|
|
|
5056
5284
|
var import_node_fs21 = require("fs");
|
|
5057
5285
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
5058
5286
|
var import_node_path34 = __toESM(require("path"), 1);
|
|
5059
|
-
var
|
|
5287
|
+
var import_types23 = require("@neat.is/types");
|
|
5060
5288
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
5061
5289
|
var LOCK_RETRY_MS = 50;
|
|
5062
5290
|
function neatHome() {
|
|
@@ -5135,10 +5363,10 @@ async function readRegistry() {
|
|
|
5135
5363
|
throw err;
|
|
5136
5364
|
}
|
|
5137
5365
|
const parsed = JSON.parse(raw);
|
|
5138
|
-
return
|
|
5366
|
+
return import_types23.RegistryFileSchema.parse(parsed);
|
|
5139
5367
|
}
|
|
5140
5368
|
async function writeRegistry(reg) {
|
|
5141
|
-
const validated =
|
|
5369
|
+
const validated = import_types23.RegistryFileSchema.parse(reg);
|
|
5142
5370
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
5143
5371
|
}
|
|
5144
5372
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -5401,11 +5629,11 @@ function registerRoutes(scope, ctx) {
|
|
|
5401
5629
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
5402
5630
|
const parsed = [];
|
|
5403
5631
|
for (const c of candidates) {
|
|
5404
|
-
const r =
|
|
5632
|
+
const r = import_types24.DivergenceTypeSchema.safeParse(c);
|
|
5405
5633
|
if (!r.success) {
|
|
5406
5634
|
return reply.code(400).send({
|
|
5407
5635
|
error: `unknown divergence type "${c}"`,
|
|
5408
|
-
allowed:
|
|
5636
|
+
allowed: import_types24.DivergenceTypeSchema.options
|
|
5409
5637
|
});
|
|
5410
5638
|
}
|
|
5411
5639
|
parsed.push(r.data);
|
|
@@ -5618,7 +5846,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5618
5846
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
5619
5847
|
let violations = await log.readAll();
|
|
5620
5848
|
if (req.query.severity) {
|
|
5621
|
-
const sev =
|
|
5849
|
+
const sev = import_types24.PolicySeveritySchema.safeParse(req.query.severity);
|
|
5622
5850
|
if (!sev.success) {
|
|
5623
5851
|
return reply.code(400).send({
|
|
5624
5852
|
error: "invalid severity",
|
|
@@ -5635,7 +5863,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5635
5863
|
scope.post("/policies/check", async (req, reply) => {
|
|
5636
5864
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5637
5865
|
if (!proj) return;
|
|
5638
|
-
const parsed =
|
|
5866
|
+
const parsed = import_types24.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
5639
5867
|
if (!parsed.success) {
|
|
5640
5868
|
return reply.code(400).send({
|
|
5641
5869
|
error: "invalid /policies/check body",
|
|
@@ -5672,14 +5900,18 @@ function registerRoutes(scope, ctx) {
|
|
|
5672
5900
|
async function buildApi(opts) {
|
|
5673
5901
|
const app = (0, import_fastify.default)({ logger: false });
|
|
5674
5902
|
await app.register(import_cors.default, { origin: true });
|
|
5903
|
+
const env = readAuthEnv();
|
|
5904
|
+
const authToken = opts.authToken ?? env.authToken;
|
|
5905
|
+
const trustProxy = opts.trustProxy ?? env.trustProxy;
|
|
5906
|
+
const publicRead = opts.publicRead ?? env.publicRead;
|
|
5675
5907
|
mountBearerAuth(app, {
|
|
5676
|
-
token:
|
|
5677
|
-
trustProxy
|
|
5678
|
-
publicRead
|
|
5908
|
+
token: authToken,
|
|
5909
|
+
trustProxy,
|
|
5910
|
+
publicRead
|
|
5679
5911
|
});
|
|
5680
5912
|
app.get("/api/config", async () => ({
|
|
5681
|
-
publicRead:
|
|
5682
|
-
authProxy:
|
|
5913
|
+
publicRead: publicRead === true,
|
|
5914
|
+
authProxy: trustProxy === true
|
|
5683
5915
|
}));
|
|
5684
5916
|
const startedAt = opts.startedAt ?? Date.now();
|
|
5685
5917
|
const registry = buildLegacyRegistry(opts);
|
|
@@ -5769,6 +6001,7 @@ async function buildApi(opts) {
|
|
|
5769
6001
|
}
|
|
5770
6002
|
|
|
5771
6003
|
// src/watch.ts
|
|
6004
|
+
init_auth();
|
|
5772
6005
|
init_otel();
|
|
5773
6006
|
init_otel_grpc();
|
|
5774
6007
|
|
|
@@ -6253,7 +6486,9 @@ async function startWatch(graph, opts) {
|
|
|
6253
6486
|
project: projectName,
|
|
6254
6487
|
onPolicyTrigger
|
|
6255
6488
|
});
|
|
6256
|
-
const
|
|
6489
|
+
const auth = readAuthEnv();
|
|
6490
|
+
const host = opts.host ?? (auth.authToken ? "0.0.0.0" : "127.0.0.1");
|
|
6491
|
+
assertBindAuthority(host, auth.authToken);
|
|
6257
6492
|
const port = opts.port ?? 8080;
|
|
6258
6493
|
const otelPort = opts.otelPort ?? 4318;
|
|
6259
6494
|
const cachePath = opts.embeddingsCachePath ?? import_node_path38.default.join(import_node_path38.default.dirname(opts.outPath), "embeddings.json");
|
|
@@ -6569,38 +6804,123 @@ var import_node_path40 = __toESM(require("path"), 1);
|
|
|
6569
6804
|
// src/installers/templates.ts
|
|
6570
6805
|
init_cjs_shims();
|
|
6571
6806
|
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
6807
|
+
var OTEL_INIT_STAMP = "// neat-template-version: 3 \u2014 file-first call-site capture (ADR-089) + OTLP bearer auth (#410).";
|
|
6808
|
+
var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
|
|
6809
|
+
function neatCallsiteProcessorSource(ts) {
|
|
6810
|
+
const stackT = ts ? ": string" : "";
|
|
6811
|
+
const spanT = ts ? ": any" : "";
|
|
6812
|
+
const strOpt = ts ? ": string | undefined" : "";
|
|
6813
|
+
return `function __neatPickUserFrame(stack${stackT}) {
|
|
6814
|
+
const lines = String(stack || '').split('\\n')
|
|
6815
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6816
|
+
const raw = lines[i].trim()
|
|
6817
|
+
if (raw.indexOf('at ') !== 0) continue
|
|
6818
|
+
if (raw.indexOf('node_modules') !== -1) continue
|
|
6819
|
+
if (raw.indexOf('@opentelemetry') !== -1) continue
|
|
6820
|
+
if (raw.indexOf('node:') !== -1) continue
|
|
6821
|
+
if (raw.indexOf('NeatCallSiteSpanProcessor') !== -1) continue
|
|
6822
|
+
const bodyText = raw.slice(3)
|
|
6823
|
+
const loc = bodyText.match(/:(\\d+):(\\d+)\\)?$/)
|
|
6824
|
+
if (!loc) continue
|
|
6825
|
+
const paren = bodyText.lastIndexOf('(')
|
|
6826
|
+
let filepath${strOpt}
|
|
6827
|
+
let fn${strOpt}
|
|
6828
|
+
if (paren !== -1) {
|
|
6829
|
+
fn = bodyText.slice(0, paren).trim()
|
|
6830
|
+
if (fn.indexOf('async ') === 0) fn = fn.slice(6)
|
|
6831
|
+
if (fn.indexOf('new ') === 0) fn = fn.slice(4)
|
|
6832
|
+
filepath = bodyText.slice(paren + 1, bodyText.length - loc[0].length)
|
|
6833
|
+
} else {
|
|
6834
|
+
filepath = bodyText.slice(0, bodyText.length - loc[0].length)
|
|
6835
|
+
}
|
|
6836
|
+
if (filepath.indexOf('file://') === 0) filepath = filepath.slice(7)
|
|
6837
|
+
if (!filepath) continue
|
|
6838
|
+
return { filepath: filepath, lineno: Number(loc[1]), function: fn || undefined }
|
|
6839
|
+
}
|
|
6840
|
+
return null
|
|
6841
|
+
}
|
|
6842
|
+
|
|
6843
|
+
class NeatCallSiteSpanProcessor {
|
|
6844
|
+
onStart(span${spanT}) {
|
|
6845
|
+
if (!span || (span.kind !== 2 && span.kind !== 3)) return
|
|
6846
|
+
const frame = __neatPickUserFrame(new Error().stack)
|
|
6847
|
+
if (!frame) return
|
|
6848
|
+
span.setAttribute('code.filepath', frame.filepath)
|
|
6849
|
+
if (typeof frame.lineno === 'number') span.setAttribute('code.lineno', frame.lineno)
|
|
6850
|
+
if (frame.function) span.setAttribute('code.function', frame.function)
|
|
6851
|
+
}
|
|
6852
|
+
onEnd() {}
|
|
6853
|
+
forceFlush() { return Promise.resolve() }
|
|
6854
|
+
shutdown() { return Promise.resolve() }
|
|
6855
|
+
}`;
|
|
6856
|
+
}
|
|
6857
|
+
var CALLSITE_PROCESSOR_JS = neatCallsiteProcessorSource(false);
|
|
6858
|
+
var CALLSITE_PROCESSOR_TS = neatCallsiteProcessorSource(true);
|
|
6859
|
+
function neatRegisterCallsiteSource(ts) {
|
|
6860
|
+
const providerT = ts ? ": any" : "";
|
|
6861
|
+
return `try {
|
|
6862
|
+
const provider${providerT} = trace.getTracerProvider()
|
|
6863
|
+
const delegate = provider && typeof provider.getDelegate === 'function' ? provider.getDelegate() : provider
|
|
6864
|
+
if (delegate && typeof delegate.addSpanProcessor === 'function') {
|
|
6865
|
+
delegate.addSpanProcessor(new NeatCallSiteSpanProcessor())
|
|
6866
|
+
}
|
|
6867
|
+
} catch {
|
|
6868
|
+
// capture is best-effort; span export is unaffected
|
|
6869
|
+
}`;
|
|
6870
|
+
}
|
|
6572
6871
|
var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
|
|
6872
|
+
${OTEL_INIT_STAMP}
|
|
6573
6873
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6574
6874
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6875
|
+
${OTEL_OTLP_HEADERS_JS}
|
|
6575
6876
|
|
|
6576
6877
|
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
6577
6878
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
6879
|
+
const { trace } = require('@opentelemetry/api')
|
|
6880
|
+
|
|
6881
|
+
${CALLSITE_PROCESSOR_JS}
|
|
6578
6882
|
|
|
6579
6883
|
const instrumentations = [getNodeAutoInstrumentations()]
|
|
6580
6884
|
__INSTRUMENTATION_BLOCK__
|
|
6581
|
-
new NodeSDK({ instrumentations })
|
|
6885
|
+
const sdk = new NodeSDK({ instrumentations })
|
|
6886
|
+
sdk.start()
|
|
6887
|
+
${neatRegisterCallsiteSource(false)}
|
|
6582
6888
|
`;
|
|
6583
6889
|
var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
|
|
6890
|
+
${OTEL_INIT_STAMP}
|
|
6584
6891
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6585
6892
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6893
|
+
${OTEL_OTLP_HEADERS_JS}
|
|
6586
6894
|
|
|
6587
6895
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6588
6896
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6897
|
+
import { trace } from '@opentelemetry/api'
|
|
6898
|
+
|
|
6899
|
+
${CALLSITE_PROCESSOR_JS}
|
|
6589
6900
|
|
|
6590
6901
|
const instrumentations = [getNodeAutoInstrumentations()]
|
|
6591
6902
|
__INSTRUMENTATION_BLOCK__
|
|
6592
|
-
new NodeSDK({ instrumentations })
|
|
6903
|
+
const sdk = new NodeSDK({ instrumentations })
|
|
6904
|
+
sdk.start()
|
|
6905
|
+
${neatRegisterCallsiteSource(false)}
|
|
6593
6906
|
`;
|
|
6594
6907
|
var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
|
|
6908
|
+
${OTEL_INIT_STAMP}
|
|
6595
6909
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6596
6910
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6911
|
+
${OTEL_OTLP_HEADERS_JS}
|
|
6597
6912
|
|
|
6598
6913
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6599
6914
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6915
|
+
import { trace } from '@opentelemetry/api'
|
|
6916
|
+
|
|
6917
|
+
${CALLSITE_PROCESSOR_TS}
|
|
6600
6918
|
|
|
6601
6919
|
const instrumentations = [getNodeAutoInstrumentations()]
|
|
6602
6920
|
__INSTRUMENTATION_BLOCK__
|
|
6603
|
-
new NodeSDK({ instrumentations })
|
|
6921
|
+
const sdk = new NodeSDK({ instrumentations })
|
|
6922
|
+
sdk.start()
|
|
6923
|
+
${neatRegisterCallsiteSource(true)}
|
|
6604
6924
|
`;
|
|
6605
6925
|
function renderNodeOtelInit(template, serviceName, projectName, registrations = []) {
|
|
6606
6926
|
const block = registrations.length === 0 ? "" : `
|
|
@@ -6613,6 +6933,8 @@ function renderEnvNeat(serviceName, projectName) {
|
|
|
6613
6933
|
"# Generated by `neat init --apply` (ADR-069).",
|
|
6614
6934
|
`OTEL_SERVICE_NAME=${serviceName}`,
|
|
6615
6935
|
`OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/projects/${projectName}/v1/traces`,
|
|
6936
|
+
"# Set NEAT_OTEL_TOKEN to the daemon's OTLP secret to authenticate exported spans (#410).",
|
|
6937
|
+
"# NEAT_OTEL_TOKEN=",
|
|
6616
6938
|
""
|
|
6617
6939
|
].join("\n");
|
|
6618
6940
|
}
|
|
@@ -6634,6 +6956,7 @@ export async function register() {
|
|
|
6634
6956
|
var NEXT_INSTRUMENTATION_NODE_TS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
6635
6957
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6636
6958
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6959
|
+
${OTEL_OTLP_HEADERS_JS}
|
|
6637
6960
|
|
|
6638
6961
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6639
6962
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
@@ -6645,6 +6968,7 @@ new NodeSDK({ instrumentations }).start()
|
|
|
6645
6968
|
var NEXT_INSTRUMENTATION_NODE_JS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
6646
6969
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6647
6970
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6971
|
+
${OTEL_OTLP_HEADERS_JS}
|
|
6648
6972
|
|
|
6649
6973
|
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
6650
6974
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
@@ -6663,6 +6987,7 @@ var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074).
|
|
|
6663
6987
|
var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
6664
6988
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6665
6989
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6990
|
+
${OTEL_OTLP_HEADERS_JS}
|
|
6666
6991
|
|
|
6667
6992
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6668
6993
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
@@ -6676,6 +7001,7 @@ sdk.start()
|
|
|
6676
7001
|
var FRAMEWORK_OTEL_INIT_JS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
6677
7002
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6678
7003
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
7004
|
+
${OTEL_OTLP_HEADERS_JS}
|
|
6679
7005
|
|
|
6680
7006
|
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
6681
7007
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
@@ -6824,6 +7150,23 @@ async function exists2(p) {
|
|
|
6824
7150
|
return false;
|
|
6825
7151
|
}
|
|
6826
7152
|
}
|
|
7153
|
+
async function readFileMaybe(p) {
|
|
7154
|
+
try {
|
|
7155
|
+
return await import_node_fs25.promises.readFile(p, "utf8");
|
|
7156
|
+
} catch {
|
|
7157
|
+
return null;
|
|
7158
|
+
}
|
|
7159
|
+
}
|
|
7160
|
+
async function planOtelInitGeneration(file, contents) {
|
|
7161
|
+
const existing = await readFileMaybe(file);
|
|
7162
|
+
if (existing === null) {
|
|
7163
|
+
return { file, contents, skipIfExists: true };
|
|
7164
|
+
}
|
|
7165
|
+
if (existing.includes(OTEL_INIT_HEADER) && !existing.includes(OTEL_INIT_STAMP)) {
|
|
7166
|
+
return { file, contents, skipIfExists: false };
|
|
7167
|
+
}
|
|
7168
|
+
return null;
|
|
7169
|
+
}
|
|
6827
7170
|
async function detect(serviceDir) {
|
|
6828
7171
|
const pkg = await readPackageJson(serviceDir);
|
|
6829
7172
|
return pkg !== null && typeof pkg.name === "string";
|
|
@@ -7554,18 +7897,11 @@ async function plan(serviceDir, opts) {
|
|
|
7554
7897
|
const projectName = projectToken(pkg, serviceDir, project);
|
|
7555
7898
|
const registrations = nonBundled.map((i) => i.registration);
|
|
7556
7899
|
const generatedFiles = [];
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
svcName,
|
|
7563
|
-
projectName,
|
|
7564
|
-
registrations
|
|
7565
|
-
),
|
|
7566
|
-
skipIfExists: true
|
|
7567
|
-
});
|
|
7568
|
-
}
|
|
7900
|
+
const otelInitGen = await planOtelInitGeneration(
|
|
7901
|
+
otelInitFile,
|
|
7902
|
+
renderNodeOtelInit(otelInitContents(flavor), svcName, projectName, registrations)
|
|
7903
|
+
);
|
|
7904
|
+
if (otelInitGen) generatedFiles.push(otelInitGen);
|
|
7569
7905
|
if (!await exists2(envNeatFile)) {
|
|
7570
7906
|
generatedFiles.push({
|
|
7571
7907
|
file: envNeatFile,
|
|
@@ -8608,7 +8944,7 @@ var import_node_path44 = __toESM(require("path"), 1);
|
|
|
8608
8944
|
|
|
8609
8945
|
// src/cli-client.ts
|
|
8610
8946
|
init_cjs_shims();
|
|
8611
|
-
var
|
|
8947
|
+
var import_types25 = require("@neat.is/types");
|
|
8612
8948
|
var HttpError = class extends Error {
|
|
8613
8949
|
constructor(status2, message, responseBody = "") {
|
|
8614
8950
|
super(message);
|
|
@@ -8746,7 +9082,7 @@ async function runBlastRadius(client, input) {
|
|
|
8746
9082
|
}
|
|
8747
9083
|
}
|
|
8748
9084
|
function formatBlastEntry(n) {
|
|
8749
|
-
const tag = n.edgeProvenance ===
|
|
9085
|
+
const tag = n.edgeProvenance === import_types25.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
|
|
8750
9086
|
return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
|
|
8751
9087
|
}
|
|
8752
9088
|
async function runDependencies(client, input) {
|
|
@@ -8792,9 +9128,9 @@ async function runObservedDependencies(client, input) {
|
|
|
8792
9128
|
const edges = await client.get(
|
|
8793
9129
|
projectPath(input.project, `/graph/edges/${encodeURIComponent(input.nodeId)}`)
|
|
8794
9130
|
);
|
|
8795
|
-
const observed = edges.outbound.filter((e) => e.provenance ===
|
|
9131
|
+
const observed = edges.outbound.filter((e) => e.provenance === import_types25.Provenance.OBSERVED);
|
|
8796
9132
|
if (observed.length === 0) {
|
|
8797
|
-
const hasExtracted = edges.outbound.some((e) => e.provenance ===
|
|
9133
|
+
const hasExtracted = edges.outbound.some((e) => e.provenance === import_types25.Provenance.EXTRACTED);
|
|
8798
9134
|
const note = hasExtracted ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
|
|
8799
9135
|
return { summary: `No OBSERVED dependencies for ${input.nodeId}.${note}` };
|
|
8800
9136
|
}
|
|
@@ -8802,7 +9138,7 @@ async function runObservedDependencies(client, input) {
|
|
|
8802
9138
|
return {
|
|
8803
9139
|
summary: `${input.nodeId} has ${observed.length} runtime dependenc${observed.length === 1 ? "y" : "ies"} confirmed by OTel.`,
|
|
8804
9140
|
block: blockLines.join("\n"),
|
|
8805
|
-
provenance:
|
|
9141
|
+
provenance: import_types25.Provenance.OBSERVED
|
|
8806
9142
|
};
|
|
8807
9143
|
} catch (err) {
|
|
8808
9144
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -8856,7 +9192,7 @@ async function runIncidents(client, input) {
|
|
|
8856
9192
|
return {
|
|
8857
9193
|
summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
|
|
8858
9194
|
block: blockLines.join("\n"),
|
|
8859
|
-
provenance:
|
|
9195
|
+
provenance: import_types25.Provenance.OBSERVED
|
|
8860
9196
|
};
|
|
8861
9197
|
} catch (err) {
|
|
8862
9198
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -8965,7 +9301,7 @@ async function runStaleEdges(client, input) {
|
|
|
8965
9301
|
return {
|
|
8966
9302
|
summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
|
|
8967
9303
|
block: blockLines.join("\n"),
|
|
8968
|
-
provenance:
|
|
9304
|
+
provenance: import_types25.Provenance.STALE
|
|
8969
9305
|
};
|
|
8970
9306
|
}
|
|
8971
9307
|
async function runPolicies(client, input) {
|
|
@@ -9279,7 +9615,7 @@ async function runSync(opts) {
|
|
|
9279
9615
|
}
|
|
9280
9616
|
|
|
9281
9617
|
// src/cli.ts
|
|
9282
|
-
var
|
|
9618
|
+
var import_types26 = require("@neat.is/types");
|
|
9283
9619
|
function usage() {
|
|
9284
9620
|
console.log("usage: neat <command> [args] [--project <name>]");
|
|
9285
9621
|
console.log("");
|
|
@@ -9519,7 +9855,7 @@ function printBanner() {
|
|
|
9519
9855
|
console.log("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ");
|
|
9520
9856
|
console.log("");
|
|
9521
9857
|
console.log(" Network Expressive Architecting Tool");
|
|
9522
|
-
console.log(
|
|
9858
|
+
console.log(` neat.is \xB7 v${readPackageVersion()} \xB7 Apache 2.0`);
|
|
9523
9859
|
console.log("");
|
|
9524
9860
|
}
|
|
9525
9861
|
function printDiscoveryReport(opts, services) {
|
|
@@ -10112,10 +10448,10 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
10112
10448
|
const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
10113
10449
|
const out = [];
|
|
10114
10450
|
for (const p of parts) {
|
|
10115
|
-
const r =
|
|
10451
|
+
const r = import_types26.DivergenceTypeSchema.safeParse(p);
|
|
10116
10452
|
if (!r.success) {
|
|
10117
10453
|
console.error(
|
|
10118
|
-
`neat divergences: unknown --type "${p}". allowed: ${
|
|
10454
|
+
`neat divergences: unknown --type "${p}". allowed: ${import_types26.DivergenceTypeSchema.options.join(", ")}`
|
|
10119
10455
|
);
|
|
10120
10456
|
return 2;
|
|
10121
10457
|
}
|
|
@@ -10164,6 +10500,7 @@ if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsW
|
|
|
10164
10500
|
CLAUDE_SKILL_CONFIG,
|
|
10165
10501
|
QUERY_VERBS,
|
|
10166
10502
|
parseArgs,
|
|
10503
|
+
printBanner,
|
|
10167
10504
|
readPackageVersion,
|
|
10168
10505
|
runInit,
|
|
10169
10506
|
runQueryVerb,
|