@neat.is/core 0.4.11 → 0.4.13
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-OJHI33LD.js → chunk-5W7H35JJ.js} +1111 -343
- package/dist/chunk-5W7H35JJ.js.map +1 -0
- package/dist/{chunk-W4RNPPB5.js → chunk-TWNJX26R.js} +2 -2
- package/dist/cli.cjs +1471 -682
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +197 -169
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1145 -380
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +1133 -368
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +1267 -502
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +4 -2
- package/dist/chunk-OJHI33LD.js.map +0 -1
- /package/dist/{chunk-W4RNPPB5.js.map → chunk-TWNJX26R.js.map} +0 -0
package/dist/neatd.cjs
CHANGED
|
@@ -57,9 +57,9 @@ function mountBearerAuth(app, opts) {
|
|
|
57
57
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
58
58
|
const publicRead = opts.publicRead === true;
|
|
59
59
|
app.addHook("preHandler", (req2, reply, done) => {
|
|
60
|
-
const
|
|
60
|
+
const path44 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
61
61
|
for (const suffix of suffixes) {
|
|
62
|
-
if (
|
|
62
|
+
if (path44 === suffix || path44.endsWith(suffix)) {
|
|
63
63
|
done();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
@@ -185,8 +185,8 @@ function reshapeGrpcRequest(req2) {
|
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
187
|
function resolveProtoRoot() {
|
|
188
|
-
const here =
|
|
189
|
-
return
|
|
188
|
+
const here = import_node_path38.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
189
|
+
return import_node_path38.default.resolve(here, "..", "proto");
|
|
190
190
|
}
|
|
191
191
|
function loadTraceService() {
|
|
192
192
|
const protoRoot = resolveProtoRoot();
|
|
@@ -254,13 +254,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
254
254
|
})
|
|
255
255
|
};
|
|
256
256
|
}
|
|
257
|
-
var import_node_url,
|
|
257
|
+
var import_node_url, import_node_path38, import_node_crypto2, grpc, protoLoader;
|
|
258
258
|
var init_otel_grpc = __esm({
|
|
259
259
|
"src/otel-grpc.ts"() {
|
|
260
260
|
"use strict";
|
|
261
261
|
init_cjs_shims();
|
|
262
262
|
import_node_url = require("url");
|
|
263
|
-
|
|
263
|
+
import_node_path38 = __toESM(require("path"), 1);
|
|
264
264
|
import_node_crypto2 = require("crypto");
|
|
265
265
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
266
266
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -370,10 +370,10 @@ function parseOtlpRequest(body) {
|
|
|
370
370
|
return out;
|
|
371
371
|
}
|
|
372
372
|
function loadProtoRoot() {
|
|
373
|
-
const here =
|
|
374
|
-
const protoRoot =
|
|
373
|
+
const here = import_node_path39.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
374
|
+
const protoRoot = import_node_path39.default.resolve(here, "..", "proto");
|
|
375
375
|
const root = new import_protobufjs.default.Root();
|
|
376
|
-
root.resolvePath = (_origin, target) =>
|
|
376
|
+
root.resolvePath = (_origin, target) => import_node_path39.default.resolve(protoRoot, target);
|
|
377
377
|
root.loadSync(
|
|
378
378
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
379
379
|
{ keepCase: true }
|
|
@@ -566,12 +566,12 @@ async function buildOtelReceiver(opts) {
|
|
|
566
566
|
};
|
|
567
567
|
return decorated;
|
|
568
568
|
}
|
|
569
|
-
var
|
|
569
|
+
var import_node_path39, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
570
570
|
var init_otel = __esm({
|
|
571
571
|
"src/otel.ts"() {
|
|
572
572
|
"use strict";
|
|
573
573
|
init_cjs_shims();
|
|
574
|
-
|
|
574
|
+
import_node_path39 = __toESM(require("path"), 1);
|
|
575
575
|
import_node_url2 = require("url");
|
|
576
576
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
577
577
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -587,14 +587,14 @@ var init_otel = __esm({
|
|
|
587
587
|
|
|
588
588
|
// src/neatd.ts
|
|
589
589
|
init_cjs_shims();
|
|
590
|
-
var
|
|
591
|
-
var
|
|
590
|
+
var import_node_fs26 = require("fs");
|
|
591
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
592
592
|
var import_node_module = require("module");
|
|
593
593
|
|
|
594
594
|
// src/daemon.ts
|
|
595
595
|
init_cjs_shims();
|
|
596
|
-
var
|
|
597
|
-
var
|
|
596
|
+
var import_node_fs25 = require("fs");
|
|
597
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
598
598
|
|
|
599
599
|
// src/graph.ts
|
|
600
600
|
init_cjs_shims();
|
|
@@ -1073,19 +1073,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1073
1073
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1074
1074
|
let best = { path: [start], edges: [] };
|
|
1075
1075
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1076
|
-
function step(node,
|
|
1077
|
-
if (
|
|
1078
|
-
best = { path: [...
|
|
1076
|
+
function step(node, path44, edges) {
|
|
1077
|
+
if (path44.length > best.path.length) {
|
|
1078
|
+
best = { path: [...path44], edges: [...edges] };
|
|
1079
1079
|
}
|
|
1080
|
-
if (
|
|
1080
|
+
if (path44.length - 1 >= maxDepth) return;
|
|
1081
1081
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1082
1082
|
for (const [srcId, edge] of incoming) {
|
|
1083
1083
|
if (visited.has(srcId)) continue;
|
|
1084
1084
|
visited.add(srcId);
|
|
1085
|
-
|
|
1085
|
+
path44.push(srcId);
|
|
1086
1086
|
edges.push(edge);
|
|
1087
|
-
step(srcId,
|
|
1088
|
-
|
|
1087
|
+
step(srcId, path44, edges);
|
|
1088
|
+
path44.pop();
|
|
1089
1089
|
edges.pop();
|
|
1090
1090
|
visited.delete(srcId);
|
|
1091
1091
|
}
|
|
@@ -1895,7 +1895,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1895
1895
|
graph.addNode(id, node);
|
|
1896
1896
|
return id;
|
|
1897
1897
|
}
|
|
1898
|
-
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
1898
|
+
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
1899
1899
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
1900
1900
|
const id = makeObservedEdgeId(type, source, target);
|
|
1901
1901
|
if (graph.hasEdge(id)) {
|
|
@@ -1932,7 +1932,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1932
1932
|
confidence: (0, import_types3.confidenceForObservedSignal)(signal),
|
|
1933
1933
|
lastObserved: ts,
|
|
1934
1934
|
callCount: 1,
|
|
1935
|
-
signal
|
|
1935
|
+
signal,
|
|
1936
|
+
// Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
|
|
1937
|
+
// Only set when code.filepath was present on the span — never fabricated.
|
|
1938
|
+
...evidence ? { evidence } : {}
|
|
1936
1939
|
};
|
|
1937
1940
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
1938
1941
|
return { edge, created: true };
|
|
@@ -2026,6 +2029,7 @@ async function handleSpan(ctx, span) {
|
|
|
2026
2029
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2027
2030
|
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
2028
2031
|
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
2032
|
+
const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
|
|
2029
2033
|
let affectedNode = sourceId;
|
|
2030
2034
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2031
2035
|
if (span.dbSystem) {
|
|
@@ -2039,7 +2043,8 @@ async function handleSpan(ctx, span) {
|
|
|
2039
2043
|
observedSource(),
|
|
2040
2044
|
targetId,
|
|
2041
2045
|
ts,
|
|
2042
|
-
isError
|
|
2046
|
+
isError,
|
|
2047
|
+
callSiteEvidence
|
|
2043
2048
|
);
|
|
2044
2049
|
if (result) affectedNode = targetId;
|
|
2045
2050
|
}
|
|
@@ -2055,7 +2060,8 @@ async function handleSpan(ctx, span) {
|
|
|
2055
2060
|
observedSource(),
|
|
2056
2061
|
targetId,
|
|
2057
2062
|
ts,
|
|
2058
|
-
isError
|
|
2063
|
+
isError,
|
|
2064
|
+
callSiteEvidence
|
|
2059
2065
|
);
|
|
2060
2066
|
affectedNode = targetId;
|
|
2061
2067
|
resolvedViaAddress = true;
|
|
@@ -2067,7 +2073,8 @@ async function handleSpan(ctx, span) {
|
|
|
2067
2073
|
observedSource(),
|
|
2068
2074
|
frontierNodeId,
|
|
2069
2075
|
ts,
|
|
2070
|
-
isError
|
|
2076
|
+
isError,
|
|
2077
|
+
callSiteEvidence
|
|
2071
2078
|
);
|
|
2072
2079
|
affectedNode = frontierNodeId;
|
|
2073
2080
|
resolvedViaAddress = true;
|
|
@@ -2943,16 +2950,433 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2943
2950
|
await collectK8sAliases(graph, scanPath, byName);
|
|
2944
2951
|
}
|
|
2945
2952
|
|
|
2946
|
-
// src/extract/
|
|
2953
|
+
// src/extract/files.ts
|
|
2947
2954
|
init_cjs_shims();
|
|
2948
|
-
|
|
2955
|
+
|
|
2956
|
+
// src/extract/calls/shared.ts
|
|
2957
|
+
init_cjs_shims();
|
|
2958
|
+
var import_node_fs10 = require("fs");
|
|
2959
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
2949
2960
|
var import_types7 = require("@neat.is/types");
|
|
2961
|
+
async function walkSourceFiles(dir) {
|
|
2962
|
+
const out = [];
|
|
2963
|
+
async function walk(current) {
|
|
2964
|
+
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2965
|
+
for (const entry2 of entries) {
|
|
2966
|
+
const full = import_node_path10.default.join(current, entry2.name);
|
|
2967
|
+
if (entry2.isDirectory()) {
|
|
2968
|
+
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
2969
|
+
if (await isPythonVenvDir(full)) continue;
|
|
2970
|
+
await walk(full);
|
|
2971
|
+
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name))) {
|
|
2972
|
+
out.push(full);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
await walk(dir);
|
|
2977
|
+
return out;
|
|
2978
|
+
}
|
|
2979
|
+
async function loadSourceFiles(dir) {
|
|
2980
|
+
const paths = await walkSourceFiles(dir);
|
|
2981
|
+
const out = [];
|
|
2982
|
+
for (const p of paths) {
|
|
2983
|
+
try {
|
|
2984
|
+
const content = await import_node_fs10.promises.readFile(p, "utf8");
|
|
2985
|
+
out.push({ path: p, content });
|
|
2986
|
+
} catch {
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
return out;
|
|
2990
|
+
}
|
|
2991
|
+
function lineOf(text, needle) {
|
|
2992
|
+
const idx = text.indexOf(needle);
|
|
2993
|
+
if (idx < 0) return 1;
|
|
2994
|
+
return text.slice(0, idx).split("\n").length;
|
|
2995
|
+
}
|
|
2996
|
+
function snippet(text, line) {
|
|
2997
|
+
const lines = text.split("\n");
|
|
2998
|
+
return (lines[line - 1] ?? "").trim();
|
|
2999
|
+
}
|
|
3000
|
+
function toPosix2(p) {
|
|
3001
|
+
return p.split("\\").join("/");
|
|
3002
|
+
}
|
|
3003
|
+
function languageForPath(relPath) {
|
|
3004
|
+
switch (import_node_path10.default.extname(relPath).toLowerCase()) {
|
|
3005
|
+
case ".py":
|
|
3006
|
+
return "python";
|
|
3007
|
+
case ".ts":
|
|
3008
|
+
case ".tsx":
|
|
3009
|
+
return "typescript";
|
|
3010
|
+
case ".js":
|
|
3011
|
+
case ".jsx":
|
|
3012
|
+
case ".mjs":
|
|
3013
|
+
case ".cjs":
|
|
3014
|
+
return "javascript";
|
|
3015
|
+
default:
|
|
3016
|
+
return void 0;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3020
|
+
let nodesAdded = 0;
|
|
3021
|
+
let edgesAdded = 0;
|
|
3022
|
+
const fileNodeId = (0, import_types7.fileId)(serviceName, relPath);
|
|
3023
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3024
|
+
const language = languageForPath(relPath);
|
|
3025
|
+
const node = {
|
|
3026
|
+
id: fileNodeId,
|
|
3027
|
+
type: import_types7.NodeType.FileNode,
|
|
3028
|
+
service: serviceName,
|
|
3029
|
+
path: relPath,
|
|
3030
|
+
...language ? { language } : {},
|
|
3031
|
+
discoveredVia: "static"
|
|
3032
|
+
};
|
|
3033
|
+
graph.addNode(fileNodeId, node);
|
|
3034
|
+
nodesAdded++;
|
|
3035
|
+
}
|
|
3036
|
+
const containsId = (0, import_types7.extractedEdgeId)(serviceNodeId, fileNodeId, import_types7.EdgeType.CONTAINS);
|
|
3037
|
+
if (!graph.hasEdge(containsId)) {
|
|
3038
|
+
const edge = {
|
|
3039
|
+
id: containsId,
|
|
3040
|
+
source: serviceNodeId,
|
|
3041
|
+
target: fileNodeId,
|
|
3042
|
+
type: import_types7.EdgeType.CONTAINS,
|
|
3043
|
+
provenance: import_types7.Provenance.EXTRACTED,
|
|
3044
|
+
confidence: (0, import_types7.confidenceForExtracted)("structural"),
|
|
3045
|
+
evidence: { file: relPath }
|
|
3046
|
+
};
|
|
3047
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3048
|
+
edgesAdded++;
|
|
3049
|
+
}
|
|
3050
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
// src/extract/files.ts
|
|
3054
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
3055
|
+
async function addFiles(graph, services) {
|
|
3056
|
+
let nodesAdded = 0;
|
|
3057
|
+
let edgesAdded = 0;
|
|
3058
|
+
for (const service of services) {
|
|
3059
|
+
const filePaths = await walkSourceFiles(service.dir);
|
|
3060
|
+
for (const filePath of filePaths) {
|
|
3061
|
+
const relPath = toPosix2(import_node_path11.default.relative(service.dir, filePath));
|
|
3062
|
+
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
3063
|
+
graph,
|
|
3064
|
+
service.pkg.name,
|
|
3065
|
+
service.node.id,
|
|
3066
|
+
relPath
|
|
3067
|
+
);
|
|
3068
|
+
nodesAdded += n;
|
|
3069
|
+
edgesAdded += e;
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
return { nodesAdded, edgesAdded };
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
// src/extract/imports.ts
|
|
3076
|
+
init_cjs_shims();
|
|
3077
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
3078
|
+
var import_node_fs11 = require("fs");
|
|
3079
|
+
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3080
|
+
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3081
|
+
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3082
|
+
var import_types8 = require("@neat.is/types");
|
|
3083
|
+
var PARSE_CHUNK = 16384;
|
|
3084
|
+
function parseSource(parser, source) {
|
|
3085
|
+
return parser.parse(
|
|
3086
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
|
|
3087
|
+
);
|
|
3088
|
+
}
|
|
3089
|
+
function makeJsParser() {
|
|
3090
|
+
const p = new import_tree_sitter.default();
|
|
3091
|
+
p.setLanguage(import_tree_sitter_javascript.default);
|
|
3092
|
+
return p;
|
|
3093
|
+
}
|
|
3094
|
+
function makePyParser() {
|
|
3095
|
+
const p = new import_tree_sitter.default();
|
|
3096
|
+
p.setLanguage(import_tree_sitter_python.default);
|
|
3097
|
+
return p;
|
|
3098
|
+
}
|
|
3099
|
+
function stringLiteralText(node) {
|
|
3100
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3101
|
+
const child = node.child(i);
|
|
3102
|
+
if (child?.type === "string_fragment") return child.text;
|
|
3103
|
+
}
|
|
3104
|
+
const raw = node.text;
|
|
3105
|
+
if (raw.length >= 2) return raw.slice(1, -1);
|
|
3106
|
+
return raw.length === 0 ? null : "";
|
|
3107
|
+
}
|
|
3108
|
+
function clipSnippet(text) {
|
|
3109
|
+
const oneLine = text.split("\n")[0] ?? text;
|
|
3110
|
+
return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
|
|
3111
|
+
}
|
|
3112
|
+
function collectJsImports(node, out) {
|
|
3113
|
+
if (node.type === "import_statement") {
|
|
3114
|
+
const source = node.childForFieldName("source");
|
|
3115
|
+
if (source) {
|
|
3116
|
+
const specifier = stringLiteralText(source);
|
|
3117
|
+
if (specifier) {
|
|
3118
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
return;
|
|
3122
|
+
}
|
|
3123
|
+
if (node.type === "call_expression") {
|
|
3124
|
+
const fn = node.childForFieldName("function");
|
|
3125
|
+
if (fn?.type === "identifier" && fn.text === "require") {
|
|
3126
|
+
const args = node.childForFieldName("arguments");
|
|
3127
|
+
const firstArg = args?.namedChild(0);
|
|
3128
|
+
if (firstArg?.type === "string") {
|
|
3129
|
+
const specifier = stringLiteralText(firstArg);
|
|
3130
|
+
if (specifier) {
|
|
3131
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3137
|
+
const child = node.namedChild(i);
|
|
3138
|
+
if (child) collectJsImports(child, out);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
function collectPyImports(node, out) {
|
|
3142
|
+
if (node.type === "import_from_statement") {
|
|
3143
|
+
let level = 0;
|
|
3144
|
+
let modulePath = "";
|
|
3145
|
+
let pastFrom = false;
|
|
3146
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3147
|
+
const child = node.child(i);
|
|
3148
|
+
if (!child) continue;
|
|
3149
|
+
if (!pastFrom) {
|
|
3150
|
+
if (child.type === "from") pastFrom = true;
|
|
3151
|
+
continue;
|
|
3152
|
+
}
|
|
3153
|
+
if (child.type === "import") break;
|
|
3154
|
+
if (child.type === "relative_import") {
|
|
3155
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
3156
|
+
const rc = child.child(j);
|
|
3157
|
+
if (!rc) continue;
|
|
3158
|
+
if (rc.type === "import_prefix") {
|
|
3159
|
+
for (let k = 0; k < rc.childCount; k++) {
|
|
3160
|
+
if (rc.child(k)?.type === ".") level++;
|
|
3161
|
+
}
|
|
3162
|
+
} else if (rc.type === "dotted_name") modulePath = rc.text;
|
|
3163
|
+
}
|
|
3164
|
+
break;
|
|
3165
|
+
}
|
|
3166
|
+
if (child.type === "dotted_name") {
|
|
3167
|
+
modulePath = child.text;
|
|
3168
|
+
break;
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
if (level > 0 || modulePath) {
|
|
3172
|
+
out.push({ modulePath, level, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3176
|
+
const child = node.namedChild(i);
|
|
3177
|
+
if (child) collectPyImports(child, out);
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
async function fileExists(p) {
|
|
3181
|
+
try {
|
|
3182
|
+
await import_node_fs11.promises.access(p);
|
|
3183
|
+
return true;
|
|
3184
|
+
} catch {
|
|
3185
|
+
return false;
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
function isWithinServiceDir(candidate, serviceDir) {
|
|
3189
|
+
const rel = import_node_path12.default.relative(serviceDir, candidate);
|
|
3190
|
+
return rel !== "" && !rel.startsWith("..") && !import_node_path12.default.isAbsolute(rel);
|
|
3191
|
+
}
|
|
3192
|
+
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
3193
|
+
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
3194
|
+
async function firstExistingCandidate(base, serviceDir) {
|
|
3195
|
+
for (const ext of JS_EXTENSIONS) {
|
|
3196
|
+
const candidate = base + ext;
|
|
3197
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
3198
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
for (const indexFile of JS_INDEX_FILES) {
|
|
3202
|
+
const candidate = import_node_path12.default.join(base, indexFile);
|
|
3203
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
3204
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
return null;
|
|
3208
|
+
}
|
|
3209
|
+
async function loadTsPathConfig(serviceDir) {
|
|
3210
|
+
const tsconfigPath = import_node_path12.default.join(serviceDir, "tsconfig.json");
|
|
3211
|
+
let raw;
|
|
3212
|
+
try {
|
|
3213
|
+
raw = await import_node_fs11.promises.readFile(tsconfigPath, "utf8");
|
|
3214
|
+
} catch {
|
|
3215
|
+
return null;
|
|
3216
|
+
}
|
|
3217
|
+
try {
|
|
3218
|
+
const parsed = JSON.parse(raw);
|
|
3219
|
+
const paths = parsed.compilerOptions?.paths;
|
|
3220
|
+
if (!paths || Object.keys(paths).length === 0) return null;
|
|
3221
|
+
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
3222
|
+
return { paths, baseDir: baseUrl ? import_node_path12.default.resolve(serviceDir, baseUrl) : serviceDir };
|
|
3223
|
+
} catch (err) {
|
|
3224
|
+
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
3225
|
+
return null;
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
async function resolveTsAlias(specifier, config, serviceDir) {
|
|
3229
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
3230
|
+
let suffix = null;
|
|
3231
|
+
if (pattern === specifier) {
|
|
3232
|
+
suffix = "";
|
|
3233
|
+
} else if (pattern.endsWith("/*")) {
|
|
3234
|
+
const prefix = pattern.slice(0, -1);
|
|
3235
|
+
if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
|
|
3236
|
+
}
|
|
3237
|
+
if (suffix === null) continue;
|
|
3238
|
+
for (const target of targets) {
|
|
3239
|
+
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
3240
|
+
const resolvedBase = import_node_path12.default.resolve(config.baseDir, targetBase, suffix);
|
|
3241
|
+
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
3242
|
+
if (hit) return hit;
|
|
3243
|
+
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
|
|
3244
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, resolvedBase));
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
return null;
|
|
3249
|
+
}
|
|
3250
|
+
async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
|
|
3251
|
+
if (!specifier) return null;
|
|
3252
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
3253
|
+
const base = import_node_path12.default.resolve(importerDir, specifier);
|
|
3254
|
+
const ext = import_node_path12.default.extname(specifier);
|
|
3255
|
+
if (ext) {
|
|
3256
|
+
if (ext === ".js" || ext === ".jsx") {
|
|
3257
|
+
const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
|
|
3258
|
+
const tsSibling = base.slice(0, -ext.length) + tsExt;
|
|
3259
|
+
if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
|
|
3260
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, tsSibling));
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
|
|
3264
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, base));
|
|
3265
|
+
}
|
|
3266
|
+
return null;
|
|
3267
|
+
}
|
|
3268
|
+
return firstExistingCandidate(base, serviceDir);
|
|
3269
|
+
}
|
|
3270
|
+
if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
|
|
3271
|
+
return null;
|
|
3272
|
+
}
|
|
3273
|
+
async function resolvePyImport(imp, importerPath, serviceDir) {
|
|
3274
|
+
if (!imp.modulePath) return null;
|
|
3275
|
+
const relPath = imp.modulePath.split(".").join("/");
|
|
3276
|
+
let baseDir;
|
|
3277
|
+
if (imp.level > 0) {
|
|
3278
|
+
baseDir = import_node_path12.default.dirname(importerPath);
|
|
3279
|
+
for (let i = 1; i < imp.level; i++) baseDir = import_node_path12.default.dirname(baseDir);
|
|
3280
|
+
} else {
|
|
3281
|
+
baseDir = serviceDir;
|
|
3282
|
+
}
|
|
3283
|
+
const candidates = [import_node_path12.default.join(baseDir, `${relPath}.py`), import_node_path12.default.join(baseDir, relPath, "__init__.py")];
|
|
3284
|
+
for (const candidate of candidates) {
|
|
3285
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
3286
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
return null;
|
|
3290
|
+
}
|
|
3291
|
+
function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
|
|
3292
|
+
const importeeFileId = (0, import_types8.fileId)(serviceName, importeeRelPath);
|
|
3293
|
+
if (!graph.hasNode(importeeFileId)) return 0;
|
|
3294
|
+
const edgeId = (0, import_types8.extractedEdgeId)(importerFileId, importeeFileId, import_types8.EdgeType.IMPORTS);
|
|
3295
|
+
if (graph.hasEdge(edgeId)) return 0;
|
|
3296
|
+
const edge = {
|
|
3297
|
+
id: edgeId,
|
|
3298
|
+
source: importerFileId,
|
|
3299
|
+
target: importeeFileId,
|
|
3300
|
+
type: import_types8.EdgeType.IMPORTS,
|
|
3301
|
+
provenance: import_types8.Provenance.EXTRACTED,
|
|
3302
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3303
|
+
evidence: { file: importerRelPath, line, snippet: snippet2 }
|
|
3304
|
+
};
|
|
3305
|
+
graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
|
|
3306
|
+
return 1;
|
|
3307
|
+
}
|
|
3308
|
+
async function addImports(graph, services) {
|
|
3309
|
+
const jsParser = makeJsParser();
|
|
3310
|
+
const pyParser = makePyParser();
|
|
3311
|
+
let edgesAdded = 0;
|
|
3312
|
+
for (const service of services) {
|
|
3313
|
+
const tsPaths = await loadTsPathConfig(service.dir);
|
|
3314
|
+
const files = await loadSourceFiles(service.dir);
|
|
3315
|
+
for (const file of files) {
|
|
3316
|
+
if (isTestPath(file.path)) continue;
|
|
3317
|
+
const relFile = toPosix2(import_node_path12.default.relative(service.dir, file.path));
|
|
3318
|
+
const importerFileId = (0, import_types8.fileId)(service.pkg.name, relFile);
|
|
3319
|
+
const isPython = import_node_path12.default.extname(file.path) === ".py";
|
|
3320
|
+
if (isPython) {
|
|
3321
|
+
let pyImports = [];
|
|
3322
|
+
try {
|
|
3323
|
+
const tree = parseSource(pyParser, file.content);
|
|
3324
|
+
collectPyImports(tree.rootNode, pyImports);
|
|
3325
|
+
} catch (err) {
|
|
3326
|
+
recordExtractionError("import extraction", file.path, err);
|
|
3327
|
+
continue;
|
|
3328
|
+
}
|
|
3329
|
+
for (const imp of pyImports) {
|
|
3330
|
+
const resolved = await resolvePyImport(imp, file.path, service.dir);
|
|
3331
|
+
if (!resolved) continue;
|
|
3332
|
+
edgesAdded += emitImportEdge(
|
|
3333
|
+
graph,
|
|
3334
|
+
service.pkg.name,
|
|
3335
|
+
importerFileId,
|
|
3336
|
+
relFile,
|
|
3337
|
+
resolved,
|
|
3338
|
+
imp.line,
|
|
3339
|
+
imp.snippet
|
|
3340
|
+
);
|
|
3341
|
+
}
|
|
3342
|
+
continue;
|
|
3343
|
+
}
|
|
3344
|
+
let jsImports = [];
|
|
3345
|
+
try {
|
|
3346
|
+
const tree = parseSource(jsParser, file.content);
|
|
3347
|
+
collectJsImports(tree.rootNode, jsImports);
|
|
3348
|
+
} catch (err) {
|
|
3349
|
+
recordExtractionError("import extraction", file.path, err);
|
|
3350
|
+
continue;
|
|
3351
|
+
}
|
|
3352
|
+
for (const imp of jsImports) {
|
|
3353
|
+
const resolved = await resolveJsImport(imp.specifier, import_node_path12.default.dirname(file.path), service.dir, tsPaths);
|
|
3354
|
+
if (!resolved) continue;
|
|
3355
|
+
edgesAdded += emitImportEdge(
|
|
3356
|
+
graph,
|
|
3357
|
+
service.pkg.name,
|
|
3358
|
+
importerFileId,
|
|
3359
|
+
relFile,
|
|
3360
|
+
resolved,
|
|
3361
|
+
imp.line,
|
|
3362
|
+
imp.snippet
|
|
3363
|
+
);
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
return { nodesAdded: 0, edgesAdded };
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
// src/extract/databases/index.ts
|
|
3371
|
+
init_cjs_shims();
|
|
3372
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
3373
|
+
var import_types9 = require("@neat.is/types");
|
|
2950
3374
|
|
|
2951
3375
|
// src/extract/databases/db-config-yaml.ts
|
|
2952
3376
|
init_cjs_shims();
|
|
2953
|
-
var
|
|
3377
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
2954
3378
|
async function parse(serviceDir) {
|
|
2955
|
-
const yamlPath =
|
|
3379
|
+
const yamlPath = import_node_path13.default.join(serviceDir, "db-config.yaml");
|
|
2956
3380
|
if (!await exists(yamlPath)) return [];
|
|
2957
3381
|
const raw = await readYaml(yamlPath);
|
|
2958
3382
|
return [
|
|
@@ -2970,13 +3394,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
2970
3394
|
|
|
2971
3395
|
// src/extract/databases/dotenv.ts
|
|
2972
3396
|
init_cjs_shims();
|
|
2973
|
-
var
|
|
2974
|
-
var
|
|
3397
|
+
var import_node_fs13 = require("fs");
|
|
3398
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
2975
3399
|
|
|
2976
3400
|
// src/extract/databases/shared.ts
|
|
2977
3401
|
init_cjs_shims();
|
|
2978
|
-
var
|
|
2979
|
-
var
|
|
3402
|
+
var import_node_fs12 = require("fs");
|
|
3403
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
2980
3404
|
function schemeToEngine(scheme) {
|
|
2981
3405
|
const s = scheme.toLowerCase().split("+")[0];
|
|
2982
3406
|
switch (s) {
|
|
@@ -3015,14 +3439,14 @@ function parseConnectionString(url) {
|
|
|
3015
3439
|
}
|
|
3016
3440
|
async function readIfExists(filePath) {
|
|
3017
3441
|
try {
|
|
3018
|
-
return await
|
|
3442
|
+
return await import_node_fs12.promises.readFile(filePath, "utf8");
|
|
3019
3443
|
} catch {
|
|
3020
3444
|
return null;
|
|
3021
3445
|
}
|
|
3022
3446
|
}
|
|
3023
3447
|
async function findFirst(serviceDir, candidates) {
|
|
3024
3448
|
for (const rel of candidates) {
|
|
3025
|
-
const abs =
|
|
3449
|
+
const abs = import_node_path14.default.join(serviceDir, rel);
|
|
3026
3450
|
const content = await readIfExists(abs);
|
|
3027
3451
|
if (content !== null) return abs;
|
|
3028
3452
|
}
|
|
@@ -3073,15 +3497,15 @@ function parseDotenvLine(line) {
|
|
|
3073
3497
|
return { key, value };
|
|
3074
3498
|
}
|
|
3075
3499
|
async function parse2(serviceDir) {
|
|
3076
|
-
const entries = await
|
|
3500
|
+
const entries = await import_node_fs13.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
3077
3501
|
const configs = [];
|
|
3078
3502
|
const seen = /* @__PURE__ */ new Set();
|
|
3079
3503
|
for (const entry2 of entries) {
|
|
3080
3504
|
if (!entry2.isFile()) continue;
|
|
3081
3505
|
const match = isConfigFile(entry2.name);
|
|
3082
3506
|
if (!match.match || match.fileType !== "env") continue;
|
|
3083
|
-
const filePath =
|
|
3084
|
-
const content = await
|
|
3507
|
+
const filePath = import_node_path15.default.join(serviceDir, entry2.name);
|
|
3508
|
+
const content = await import_node_fs13.promises.readFile(filePath, "utf8");
|
|
3085
3509
|
for (const line of content.split("\n")) {
|
|
3086
3510
|
const parsed = parseDotenvLine(line);
|
|
3087
3511
|
if (!parsed) continue;
|
|
@@ -3100,9 +3524,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
3100
3524
|
|
|
3101
3525
|
// src/extract/databases/prisma.ts
|
|
3102
3526
|
init_cjs_shims();
|
|
3103
|
-
var
|
|
3527
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
3104
3528
|
async function parse3(serviceDir) {
|
|
3105
|
-
const schemaPath =
|
|
3529
|
+
const schemaPath = import_node_path16.default.join(serviceDir, "prisma", "schema.prisma");
|
|
3106
3530
|
const content = await readIfExists(schemaPath);
|
|
3107
3531
|
if (!content) return [];
|
|
3108
3532
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -3234,10 +3658,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
3234
3658
|
|
|
3235
3659
|
// src/extract/databases/ormconfig.ts
|
|
3236
3660
|
init_cjs_shims();
|
|
3237
|
-
var
|
|
3661
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
3238
3662
|
async function parse6(serviceDir) {
|
|
3239
3663
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
3240
|
-
const abs =
|
|
3664
|
+
const abs = import_node_path17.default.join(serviceDir, candidate);
|
|
3241
3665
|
if (!await exists(abs)) continue;
|
|
3242
3666
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
3243
3667
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -3297,9 +3721,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
3297
3721
|
|
|
3298
3722
|
// src/extract/databases/sequelize.ts
|
|
3299
3723
|
init_cjs_shims();
|
|
3300
|
-
var
|
|
3724
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3301
3725
|
async function parse8(serviceDir) {
|
|
3302
|
-
const configPath =
|
|
3726
|
+
const configPath = import_node_path18.default.join(serviceDir, "config", "config.json");
|
|
3303
3727
|
if (!await exists(configPath)) return [];
|
|
3304
3728
|
const raw = await readJson(configPath);
|
|
3305
3729
|
const out = [];
|
|
@@ -3326,7 +3750,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
3326
3750
|
|
|
3327
3751
|
// src/extract/databases/docker-compose.ts
|
|
3328
3752
|
init_cjs_shims();
|
|
3329
|
-
var
|
|
3753
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3330
3754
|
function portFromService(svc) {
|
|
3331
3755
|
for (const raw of svc.ports ?? []) {
|
|
3332
3756
|
const str = String(raw);
|
|
@@ -3353,7 +3777,7 @@ function databaseFromEnv(svc) {
|
|
|
3353
3777
|
}
|
|
3354
3778
|
async function parse9(serviceDir) {
|
|
3355
3779
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3356
|
-
const abs =
|
|
3780
|
+
const abs = import_node_path19.default.join(serviceDir, name);
|
|
3357
3781
|
if (!await exists(abs)) continue;
|
|
3358
3782
|
const raw = await readYaml(abs);
|
|
3359
3783
|
if (!raw?.services) return [];
|
|
@@ -3394,8 +3818,8 @@ function compatibleDriversFor(engine) {
|
|
|
3394
3818
|
}
|
|
3395
3819
|
function toDatabaseNode(config) {
|
|
3396
3820
|
return {
|
|
3397
|
-
id: (0,
|
|
3398
|
-
type:
|
|
3821
|
+
id: (0, import_types9.databaseId)(config.host),
|
|
3822
|
+
type: import_types9.NodeType.DatabaseNode,
|
|
3399
3823
|
name: config.database || config.host,
|
|
3400
3824
|
engine: config.engine,
|
|
3401
3825
|
engineVersion: config.engineVersion,
|
|
@@ -3525,19 +3949,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3525
3949
|
discoveredVia: mergedDiscoveredVia
|
|
3526
3950
|
});
|
|
3527
3951
|
}
|
|
3952
|
+
const relConfigFile = toPosix2(import_node_path20.default.relative(service.dir, config.sourceFile));
|
|
3953
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3954
|
+
graph,
|
|
3955
|
+
service.pkg.name,
|
|
3956
|
+
service.node.id,
|
|
3957
|
+
relConfigFile
|
|
3958
|
+
);
|
|
3959
|
+
nodesAdded += fn;
|
|
3960
|
+
edgesAdded += fe;
|
|
3961
|
+
const evidenceFile = toPosix2(import_node_path20.default.relative(scanPath, config.sourceFile));
|
|
3528
3962
|
const edge = {
|
|
3529
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3530
|
-
source:
|
|
3963
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
|
|
3964
|
+
source: fileNodeId,
|
|
3531
3965
|
target: dbNode.id,
|
|
3532
|
-
type:
|
|
3533
|
-
provenance:
|
|
3534
|
-
confidence: (0,
|
|
3535
|
-
|
|
3536
|
-
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3537
|
-
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3538
|
-
evidence: {
|
|
3539
|
-
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3540
|
-
}
|
|
3966
|
+
type: import_types9.EdgeType.CONNECTS_TO,
|
|
3967
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3968
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3969
|
+
evidence: { file: evidenceFile }
|
|
3541
3970
|
};
|
|
3542
3971
|
if (!graph.hasEdge(edge.id)) {
|
|
3543
3972
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3563,15 +3992,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3563
3992
|
|
|
3564
3993
|
// src/extract/configs.ts
|
|
3565
3994
|
init_cjs_shims();
|
|
3566
|
-
var
|
|
3567
|
-
var
|
|
3568
|
-
var
|
|
3995
|
+
var import_node_fs14 = require("fs");
|
|
3996
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
3997
|
+
var import_types10 = require("@neat.is/types");
|
|
3569
3998
|
async function walkConfigFiles(dir) {
|
|
3570
3999
|
const out = [];
|
|
3571
4000
|
async function walk(current) {
|
|
3572
|
-
const entries = await
|
|
4001
|
+
const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
|
|
3573
4002
|
for (const entry2 of entries) {
|
|
3574
|
-
const full =
|
|
4003
|
+
const full = import_node_path21.default.join(current, entry2.name);
|
|
3575
4004
|
if (entry2.isDirectory()) {
|
|
3576
4005
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3577
4006
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -3590,26 +4019,35 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3590
4019
|
for (const service of services) {
|
|
3591
4020
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3592
4021
|
for (const file of configFiles) {
|
|
3593
|
-
const relPath =
|
|
4022
|
+
const relPath = import_node_path21.default.relative(scanPath, file);
|
|
3594
4023
|
const node = {
|
|
3595
|
-
id: (0,
|
|
3596
|
-
type:
|
|
3597
|
-
name:
|
|
4024
|
+
id: (0, import_types10.configId)(relPath),
|
|
4025
|
+
type: import_types10.NodeType.ConfigNode,
|
|
4026
|
+
name: import_node_path21.default.basename(file),
|
|
3598
4027
|
path: relPath,
|
|
3599
|
-
fileType: isConfigFile(
|
|
4028
|
+
fileType: isConfigFile(import_node_path21.default.basename(file)).fileType
|
|
3600
4029
|
};
|
|
3601
4030
|
if (!graph.hasNode(node.id)) {
|
|
3602
4031
|
graph.addNode(node.id, node);
|
|
3603
4032
|
nodesAdded++;
|
|
3604
4033
|
}
|
|
4034
|
+
const relToService = toPosix2(import_node_path21.default.relative(service.dir, file));
|
|
4035
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4036
|
+
graph,
|
|
4037
|
+
service.pkg.name,
|
|
4038
|
+
service.node.id,
|
|
4039
|
+
relToService
|
|
4040
|
+
);
|
|
4041
|
+
nodesAdded += fn;
|
|
4042
|
+
edgesAdded += fe;
|
|
3605
4043
|
const edge = {
|
|
3606
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3607
|
-
source:
|
|
4044
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types10.EdgeType.CONFIGURED_BY),
|
|
4045
|
+
source: fileNodeId,
|
|
3608
4046
|
target: node.id,
|
|
3609
|
-
type:
|
|
3610
|
-
provenance:
|
|
3611
|
-
confidence: (0,
|
|
3612
|
-
evidence: { file: relPath.split(
|
|
4047
|
+
type: import_types10.EdgeType.CONFIGURED_BY,
|
|
4048
|
+
provenance: import_types10.Provenance.EXTRACTED,
|
|
4049
|
+
confidence: (0, import_types10.confidenceForExtracted)("structural"),
|
|
4050
|
+
evidence: { file: relPath.split(import_node_path21.default.sep).join("/") }
|
|
3613
4051
|
};
|
|
3614
4052
|
if (!graph.hasEdge(edge.id)) {
|
|
3615
4053
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3622,114 +4060,15 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3622
4060
|
|
|
3623
4061
|
// src/extract/calls/index.ts
|
|
3624
4062
|
init_cjs_shims();
|
|
3625
|
-
var
|
|
4063
|
+
var import_types16 = require("@neat.is/types");
|
|
3626
4064
|
|
|
3627
4065
|
// src/extract/calls/http.ts
|
|
3628
4066
|
init_cjs_shims();
|
|
3629
|
-
var
|
|
3630
|
-
var
|
|
3631
|
-
var
|
|
3632
|
-
var
|
|
3633
|
-
var
|
|
3634
|
-
|
|
3635
|
-
// src/extract/calls/shared.ts
|
|
3636
|
-
init_cjs_shims();
|
|
3637
|
-
var import_node_fs13 = require("fs");
|
|
3638
|
-
var import_node_path19 = __toESM(require("path"), 1);
|
|
3639
|
-
var import_types9 = require("@neat.is/types");
|
|
3640
|
-
async function walkSourceFiles(dir) {
|
|
3641
|
-
const out = [];
|
|
3642
|
-
async function walk(current) {
|
|
3643
|
-
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3644
|
-
for (const entry2 of entries) {
|
|
3645
|
-
const full = import_node_path19.default.join(current, entry2.name);
|
|
3646
|
-
if (entry2.isDirectory()) {
|
|
3647
|
-
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3648
|
-
if (await isPythonVenvDir(full)) continue;
|
|
3649
|
-
await walk(full);
|
|
3650
|
-
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry2.name))) {
|
|
3651
|
-
out.push(full);
|
|
3652
|
-
}
|
|
3653
|
-
}
|
|
3654
|
-
}
|
|
3655
|
-
await walk(dir);
|
|
3656
|
-
return out;
|
|
3657
|
-
}
|
|
3658
|
-
async function loadSourceFiles(dir) {
|
|
3659
|
-
const paths = await walkSourceFiles(dir);
|
|
3660
|
-
const out = [];
|
|
3661
|
-
for (const p of paths) {
|
|
3662
|
-
try {
|
|
3663
|
-
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3664
|
-
out.push({ path: p, content });
|
|
3665
|
-
} catch {
|
|
3666
|
-
}
|
|
3667
|
-
}
|
|
3668
|
-
return out;
|
|
3669
|
-
}
|
|
3670
|
-
function lineOf(text, needle) {
|
|
3671
|
-
const idx = text.indexOf(needle);
|
|
3672
|
-
if (idx < 0) return 1;
|
|
3673
|
-
return text.slice(0, idx).split("\n").length;
|
|
3674
|
-
}
|
|
3675
|
-
function snippet(text, line) {
|
|
3676
|
-
const lines = text.split("\n");
|
|
3677
|
-
return (lines[line - 1] ?? "").trim();
|
|
3678
|
-
}
|
|
3679
|
-
function toPosix2(p) {
|
|
3680
|
-
return p.split("\\").join("/");
|
|
3681
|
-
}
|
|
3682
|
-
function languageForPath(relPath) {
|
|
3683
|
-
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3684
|
-
case ".py":
|
|
3685
|
-
return "python";
|
|
3686
|
-
case ".ts":
|
|
3687
|
-
case ".tsx":
|
|
3688
|
-
return "typescript";
|
|
3689
|
-
case ".js":
|
|
3690
|
-
case ".jsx":
|
|
3691
|
-
case ".mjs":
|
|
3692
|
-
case ".cjs":
|
|
3693
|
-
return "javascript";
|
|
3694
|
-
default:
|
|
3695
|
-
return void 0;
|
|
3696
|
-
}
|
|
3697
|
-
}
|
|
3698
|
-
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3699
|
-
let nodesAdded = 0;
|
|
3700
|
-
let edgesAdded = 0;
|
|
3701
|
-
const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
|
|
3702
|
-
if (!graph.hasNode(fileNodeId)) {
|
|
3703
|
-
const language = languageForPath(relPath);
|
|
3704
|
-
const node = {
|
|
3705
|
-
id: fileNodeId,
|
|
3706
|
-
type: import_types9.NodeType.FileNode,
|
|
3707
|
-
service: serviceName,
|
|
3708
|
-
path: relPath,
|
|
3709
|
-
...language ? { language } : {},
|
|
3710
|
-
discoveredVia: "static"
|
|
3711
|
-
};
|
|
3712
|
-
graph.addNode(fileNodeId, node);
|
|
3713
|
-
nodesAdded++;
|
|
3714
|
-
}
|
|
3715
|
-
const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
|
|
3716
|
-
if (!graph.hasEdge(containsId)) {
|
|
3717
|
-
const edge = {
|
|
3718
|
-
id: containsId,
|
|
3719
|
-
source: serviceNodeId,
|
|
3720
|
-
target: fileNodeId,
|
|
3721
|
-
type: import_types9.EdgeType.CONTAINS,
|
|
3722
|
-
provenance: import_types9.Provenance.EXTRACTED,
|
|
3723
|
-
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3724
|
-
evidence: { file: relPath }
|
|
3725
|
-
};
|
|
3726
|
-
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3727
|
-
edgesAdded++;
|
|
3728
|
-
}
|
|
3729
|
-
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3730
|
-
}
|
|
3731
|
-
|
|
3732
|
-
// src/extract/calls/http.ts
|
|
4067
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
4068
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
4069
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
4070
|
+
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
4071
|
+
var import_types11 = require("@neat.is/types");
|
|
3733
4072
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
3734
4073
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
3735
4074
|
function isInsideJsxExternalLink(node) {
|
|
@@ -3757,14 +4096,14 @@ function collectStringLiterals(node, out) {
|
|
|
3757
4096
|
if (child) collectStringLiterals(child, out);
|
|
3758
4097
|
}
|
|
3759
4098
|
}
|
|
3760
|
-
var
|
|
3761
|
-
function
|
|
4099
|
+
var PARSE_CHUNK2 = 16384;
|
|
4100
|
+
function parseSource2(parser, source) {
|
|
3762
4101
|
return parser.parse(
|
|
3763
|
-
(index) => index >= source.length ? "" : source.slice(index, index +
|
|
4102
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
3764
4103
|
);
|
|
3765
4104
|
}
|
|
3766
4105
|
function callsFromSource(source, parser, knownHosts) {
|
|
3767
|
-
const tree =
|
|
4106
|
+
const tree = parseSource2(parser, source);
|
|
3768
4107
|
const literals = [];
|
|
3769
4108
|
collectStringLiterals(tree.rootNode, literals);
|
|
3770
4109
|
const out = [];
|
|
@@ -3778,25 +4117,25 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3778
4117
|
}
|
|
3779
4118
|
return out;
|
|
3780
4119
|
}
|
|
3781
|
-
function
|
|
3782
|
-
const p = new
|
|
3783
|
-
p.setLanguage(
|
|
4120
|
+
function makeJsParser2() {
|
|
4121
|
+
const p = new import_tree_sitter2.default();
|
|
4122
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
3784
4123
|
return p;
|
|
3785
4124
|
}
|
|
3786
|
-
function
|
|
3787
|
-
const p = new
|
|
3788
|
-
p.setLanguage(
|
|
4125
|
+
function makePyParser2() {
|
|
4126
|
+
const p = new import_tree_sitter2.default();
|
|
4127
|
+
p.setLanguage(import_tree_sitter_python2.default);
|
|
3789
4128
|
return p;
|
|
3790
4129
|
}
|
|
3791
4130
|
async function addHttpCallEdges(graph, services) {
|
|
3792
|
-
const jsParser =
|
|
3793
|
-
const pyParser =
|
|
4131
|
+
const jsParser = makeJsParser2();
|
|
4132
|
+
const pyParser = makePyParser2();
|
|
3794
4133
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
3795
4134
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3796
4135
|
for (const service of services) {
|
|
3797
|
-
knownHosts.add(
|
|
4136
|
+
knownHosts.add(import_node_path22.default.basename(service.dir));
|
|
3798
4137
|
knownHosts.add(service.pkg.name);
|
|
3799
|
-
hostToNodeId.set(
|
|
4138
|
+
hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
|
|
3800
4139
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3801
4140
|
}
|
|
3802
4141
|
let nodesAdded = 0;
|
|
@@ -3806,7 +4145,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3806
4145
|
const seen = /* @__PURE__ */ new Set();
|
|
3807
4146
|
for (const file of files) {
|
|
3808
4147
|
if (isTestPath(file.path)) continue;
|
|
3809
|
-
const parser =
|
|
4148
|
+
const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3810
4149
|
let sites;
|
|
3811
4150
|
try {
|
|
3812
4151
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -3815,14 +4154,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3815
4154
|
continue;
|
|
3816
4155
|
}
|
|
3817
4156
|
if (sites.length === 0) continue;
|
|
3818
|
-
const relFile = toPosix2(
|
|
4157
|
+
const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
|
|
3819
4158
|
for (const site of sites) {
|
|
3820
4159
|
const targetId = hostToNodeId.get(site.host);
|
|
3821
4160
|
if (!targetId || targetId === service.node.id) continue;
|
|
3822
4161
|
const dedupKey = `${relFile}|${targetId}`;
|
|
3823
4162
|
if (seen.has(dedupKey)) continue;
|
|
3824
4163
|
seen.add(dedupKey);
|
|
3825
|
-
const confidence = (0,
|
|
4164
|
+
const confidence = (0, import_types11.confidenceForExtracted)("hostname-shape-match");
|
|
3826
4165
|
const ev = {
|
|
3827
4166
|
file: relFile,
|
|
3828
4167
|
line: site.line,
|
|
@@ -3836,25 +4175,25 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3836
4175
|
);
|
|
3837
4176
|
nodesAdded += n;
|
|
3838
4177
|
edgesAdded += e;
|
|
3839
|
-
if (!(0,
|
|
4178
|
+
if (!(0, import_types11.passesExtractedFloor)(confidence)) {
|
|
3840
4179
|
noteExtractedDropped({
|
|
3841
4180
|
source: fileNodeId,
|
|
3842
4181
|
target: targetId,
|
|
3843
|
-
type:
|
|
4182
|
+
type: import_types11.EdgeType.CALLS,
|
|
3844
4183
|
confidence,
|
|
3845
4184
|
confidenceKind: "hostname-shape-match",
|
|
3846
4185
|
evidence: ev
|
|
3847
4186
|
});
|
|
3848
4187
|
continue;
|
|
3849
4188
|
}
|
|
3850
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId,
|
|
4189
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types11.EdgeType.CALLS);
|
|
3851
4190
|
if (!graph.hasEdge(edgeId)) {
|
|
3852
4191
|
const edge = {
|
|
3853
4192
|
id: edgeId,
|
|
3854
4193
|
source: fileNodeId,
|
|
3855
4194
|
target: targetId,
|
|
3856
|
-
type:
|
|
3857
|
-
provenance:
|
|
4195
|
+
type: import_types11.EdgeType.CALLS,
|
|
4196
|
+
provenance: import_types11.Provenance.EXTRACTED,
|
|
3858
4197
|
confidence,
|
|
3859
4198
|
evidence: ev
|
|
3860
4199
|
};
|
|
@@ -3869,8 +4208,8 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3869
4208
|
|
|
3870
4209
|
// src/extract/calls/kafka.ts
|
|
3871
4210
|
init_cjs_shims();
|
|
3872
|
-
var
|
|
3873
|
-
var
|
|
4211
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
4212
|
+
var import_types12 = require("@neat.is/types");
|
|
3874
4213
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3875
4214
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
3876
4215
|
function findAll(re, text) {
|
|
@@ -3891,7 +4230,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3891
4230
|
seen.add(key);
|
|
3892
4231
|
const line = lineOf(file.content, topic);
|
|
3893
4232
|
out.push({
|
|
3894
|
-
infraId: (0,
|
|
4233
|
+
infraId: (0, import_types12.infraId)("kafka-topic", topic),
|
|
3895
4234
|
name: topic,
|
|
3896
4235
|
kind: "kafka-topic",
|
|
3897
4236
|
edgeType,
|
|
@@ -3900,7 +4239,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3900
4239
|
// tier (ADR-066).
|
|
3901
4240
|
confidenceKind: "verified-call-site",
|
|
3902
4241
|
evidence: {
|
|
3903
|
-
file:
|
|
4242
|
+
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
3904
4243
|
line,
|
|
3905
4244
|
snippet: snippet(file.content, line)
|
|
3906
4245
|
}
|
|
@@ -3913,8 +4252,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3913
4252
|
|
|
3914
4253
|
// src/extract/calls/redis.ts
|
|
3915
4254
|
init_cjs_shims();
|
|
3916
|
-
var
|
|
3917
|
-
var
|
|
4255
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
4256
|
+
var import_types13 = require("@neat.is/types");
|
|
3918
4257
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3919
4258
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
3920
4259
|
const out = [];
|
|
@@ -3927,7 +4266,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3927
4266
|
seen.add(host);
|
|
3928
4267
|
const line = lineOf(file.content, host);
|
|
3929
4268
|
out.push({
|
|
3930
|
-
infraId: (0,
|
|
4269
|
+
infraId: (0, import_types13.infraId)("redis", host),
|
|
3931
4270
|
name: host,
|
|
3932
4271
|
kind: "redis",
|
|
3933
4272
|
edgeType: "CALLS",
|
|
@@ -3936,7 +4275,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3936
4275
|
// support tier (ADR-066).
|
|
3937
4276
|
confidenceKind: "url-with-structural-support",
|
|
3938
4277
|
evidence: {
|
|
3939
|
-
file:
|
|
4278
|
+
file: import_node_path24.default.relative(serviceDir, file.path),
|
|
3940
4279
|
line,
|
|
3941
4280
|
snippet: snippet(file.content, line)
|
|
3942
4281
|
}
|
|
@@ -3947,8 +4286,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3947
4286
|
|
|
3948
4287
|
// src/extract/calls/aws.ts
|
|
3949
4288
|
init_cjs_shims();
|
|
3950
|
-
var
|
|
3951
|
-
var
|
|
4289
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
4290
|
+
var import_types14 = require("@neat.is/types");
|
|
3952
4291
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3953
4292
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3954
4293
|
function hasMarker(text, markers) {
|
|
@@ -3972,7 +4311,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3972
4311
|
seen.add(key);
|
|
3973
4312
|
const line = lineOf(file.content, name);
|
|
3974
4313
|
out.push({
|
|
3975
|
-
infraId: (0,
|
|
4314
|
+
infraId: (0, import_types14.infraId)(kind, name),
|
|
3976
4315
|
name,
|
|
3977
4316
|
kind,
|
|
3978
4317
|
edgeType: "CALLS",
|
|
@@ -3981,7 +4320,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3981
4320
|
// (ADR-066).
|
|
3982
4321
|
confidenceKind: "verified-call-site",
|
|
3983
4322
|
evidence: {
|
|
3984
|
-
file:
|
|
4323
|
+
file: import_node_path25.default.relative(serviceDir, file.path),
|
|
3985
4324
|
line,
|
|
3986
4325
|
snippet: snippet(file.content, line)
|
|
3987
4326
|
}
|
|
@@ -4006,13 +4345,14 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4006
4345
|
|
|
4007
4346
|
// src/extract/calls/grpc.ts
|
|
4008
4347
|
init_cjs_shims();
|
|
4009
|
-
var
|
|
4010
|
-
var
|
|
4348
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
4349
|
+
var import_types15 = require("@neat.is/types");
|
|
4011
4350
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
4012
4351
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
4013
4352
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
4014
4353
|
function isLikelyAddress(value) {
|
|
4015
4354
|
if (!value) return false;
|
|
4355
|
+
if (/\s/.test(value) || value.startsWith("{")) return false;
|
|
4016
4356
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
4017
4357
|
}
|
|
4018
4358
|
function normaliseForMatch(s) {
|
|
@@ -4055,7 +4395,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4055
4395
|
const { kind } = classified;
|
|
4056
4396
|
const line = lineOf(file.content, m[0]);
|
|
4057
4397
|
out.push({
|
|
4058
|
-
infraId: (0,
|
|
4398
|
+
infraId: (0, import_types15.infraId)(kind, name),
|
|
4059
4399
|
name,
|
|
4060
4400
|
kind,
|
|
4061
4401
|
edgeType: "CALLS",
|
|
@@ -4064,7 +4404,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4064
4404
|
// tier (ADR-066).
|
|
4065
4405
|
confidenceKind: "verified-call-site",
|
|
4066
4406
|
evidence: {
|
|
4067
|
-
file:
|
|
4407
|
+
file: import_node_path26.default.relative(serviceDir, file.path),
|
|
4068
4408
|
line,
|
|
4069
4409
|
snippet: snippet(file.content, line)
|
|
4070
4410
|
}
|
|
@@ -4077,11 +4417,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4077
4417
|
function edgeTypeFromEndpoint(ep) {
|
|
4078
4418
|
switch (ep.edgeType) {
|
|
4079
4419
|
case "PUBLISHES_TO":
|
|
4080
|
-
return
|
|
4420
|
+
return import_types16.EdgeType.PUBLISHES_TO;
|
|
4081
4421
|
case "CONSUMES_FROM":
|
|
4082
|
-
return
|
|
4422
|
+
return import_types16.EdgeType.CONSUMES_FROM;
|
|
4083
4423
|
default:
|
|
4084
|
-
return
|
|
4424
|
+
return import_types16.EdgeType.CALLS;
|
|
4085
4425
|
}
|
|
4086
4426
|
}
|
|
4087
4427
|
function isAwsKind(kind) {
|
|
@@ -4108,7 +4448,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4108
4448
|
if (!graph.hasNode(ep.infraId)) {
|
|
4109
4449
|
const node = {
|
|
4110
4450
|
id: ep.infraId,
|
|
4111
|
-
type:
|
|
4451
|
+
type: import_types16.NodeType.InfraNode,
|
|
4112
4452
|
name: ep.name,
|
|
4113
4453
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
4114
4454
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -4120,7 +4460,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4120
4460
|
nodesAdded++;
|
|
4121
4461
|
}
|
|
4122
4462
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
4123
|
-
const confidence = (0,
|
|
4463
|
+
const confidence = (0, import_types16.confidenceForExtracted)(ep.confidenceKind);
|
|
4124
4464
|
const relFile = toPosix2(ep.evidence.file);
|
|
4125
4465
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
4126
4466
|
graph,
|
|
@@ -4130,7 +4470,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4130
4470
|
);
|
|
4131
4471
|
nodesAdded += n;
|
|
4132
4472
|
edgesAdded += e;
|
|
4133
|
-
if (!(0,
|
|
4473
|
+
if (!(0, import_types16.passesExtractedFloor)(confidence)) {
|
|
4134
4474
|
noteExtractedDropped({
|
|
4135
4475
|
source: fileNodeId,
|
|
4136
4476
|
target: ep.infraId,
|
|
@@ -4150,7 +4490,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4150
4490
|
source: fileNodeId,
|
|
4151
4491
|
target: ep.infraId,
|
|
4152
4492
|
type: edgeType,
|
|
4153
|
-
provenance:
|
|
4493
|
+
provenance: import_types16.Provenance.EXTRACTED,
|
|
4154
4494
|
confidence,
|
|
4155
4495
|
evidence: ep.evidence
|
|
4156
4496
|
};
|
|
@@ -4175,16 +4515,16 @@ init_cjs_shims();
|
|
|
4175
4515
|
|
|
4176
4516
|
// src/extract/infra/docker-compose.ts
|
|
4177
4517
|
init_cjs_shims();
|
|
4178
|
-
var
|
|
4179
|
-
var
|
|
4518
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
4519
|
+
var import_types18 = require("@neat.is/types");
|
|
4180
4520
|
|
|
4181
4521
|
// src/extract/infra/shared.ts
|
|
4182
4522
|
init_cjs_shims();
|
|
4183
|
-
var
|
|
4523
|
+
var import_types17 = require("@neat.is/types");
|
|
4184
4524
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
4185
4525
|
return {
|
|
4186
|
-
id: (0,
|
|
4187
|
-
type:
|
|
4526
|
+
id: (0, import_types17.infraId)(kind, name),
|
|
4527
|
+
type: import_types17.NodeType.InfraNode,
|
|
4188
4528
|
name,
|
|
4189
4529
|
provider,
|
|
4190
4530
|
kind,
|
|
@@ -4213,7 +4553,7 @@ function dependsOnList(value) {
|
|
|
4213
4553
|
}
|
|
4214
4554
|
function serviceNameToServiceNode(name, services) {
|
|
4215
4555
|
for (const s of services) {
|
|
4216
|
-
if (s.node.name === name ||
|
|
4556
|
+
if (s.node.name === name || import_node_path27.default.basename(s.dir) === name) return s.node.id;
|
|
4217
4557
|
}
|
|
4218
4558
|
return null;
|
|
4219
4559
|
}
|
|
@@ -4222,7 +4562,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4222
4562
|
let edgesAdded = 0;
|
|
4223
4563
|
let composePath = null;
|
|
4224
4564
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
4225
|
-
const abs =
|
|
4565
|
+
const abs = import_node_path27.default.join(scanPath, name);
|
|
4226
4566
|
if (await exists(abs)) {
|
|
4227
4567
|
composePath = abs;
|
|
4228
4568
|
break;
|
|
@@ -4235,13 +4575,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4235
4575
|
} catch (err) {
|
|
4236
4576
|
recordExtractionError(
|
|
4237
4577
|
"infra docker-compose",
|
|
4238
|
-
|
|
4578
|
+
import_node_path27.default.relative(scanPath, composePath),
|
|
4239
4579
|
err
|
|
4240
4580
|
);
|
|
4241
4581
|
return { nodesAdded, edgesAdded };
|
|
4242
4582
|
}
|
|
4243
4583
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
4244
|
-
const evidenceFile =
|
|
4584
|
+
const evidenceFile = import_node_path27.default.relative(scanPath, composePath).split(import_node_path27.default.sep).join("/");
|
|
4245
4585
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
4246
4586
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
4247
4587
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -4263,15 +4603,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4263
4603
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
4264
4604
|
const targetId = composeNameToNodeId.get(dep);
|
|
4265
4605
|
if (!targetId) continue;
|
|
4266
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
4606
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types18.EdgeType.DEPENDS_ON);
|
|
4267
4607
|
if (graph.hasEdge(edgeId)) continue;
|
|
4268
4608
|
const edge = {
|
|
4269
4609
|
id: edgeId,
|
|
4270
4610
|
source: sourceId,
|
|
4271
4611
|
target: targetId,
|
|
4272
|
-
type:
|
|
4273
|
-
provenance:
|
|
4274
|
-
confidence: (0,
|
|
4612
|
+
type: import_types18.EdgeType.DEPENDS_ON,
|
|
4613
|
+
provenance: import_types18.Provenance.EXTRACTED,
|
|
4614
|
+
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4275
4615
|
evidence: { file: evidenceFile }
|
|
4276
4616
|
};
|
|
4277
4617
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4283,9 +4623,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4283
4623
|
|
|
4284
4624
|
// src/extract/infra/dockerfile.ts
|
|
4285
4625
|
init_cjs_shims();
|
|
4286
|
-
var
|
|
4287
|
-
var
|
|
4288
|
-
var
|
|
4626
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
4627
|
+
var import_node_fs15 = require("fs");
|
|
4628
|
+
var import_types19 = require("@neat.is/types");
|
|
4289
4629
|
function runtimeImage(content) {
|
|
4290
4630
|
const lines = content.split("\n");
|
|
4291
4631
|
let last = null;
|
|
@@ -4304,15 +4644,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4304
4644
|
let nodesAdded = 0;
|
|
4305
4645
|
let edgesAdded = 0;
|
|
4306
4646
|
for (const service of services) {
|
|
4307
|
-
const dockerfilePath =
|
|
4647
|
+
const dockerfilePath = import_node_path28.default.join(service.dir, "Dockerfile");
|
|
4308
4648
|
if (!await exists(dockerfilePath)) continue;
|
|
4309
4649
|
let content;
|
|
4310
4650
|
try {
|
|
4311
|
-
content = await
|
|
4651
|
+
content = await import_node_fs15.promises.readFile(dockerfilePath, "utf8");
|
|
4312
4652
|
} catch (err) {
|
|
4313
4653
|
recordExtractionError(
|
|
4314
4654
|
"infra dockerfile",
|
|
4315
|
-
|
|
4655
|
+
import_node_path28.default.relative(scanPath, dockerfilePath),
|
|
4316
4656
|
err
|
|
4317
4657
|
);
|
|
4318
4658
|
continue;
|
|
@@ -4324,17 +4664,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4324
4664
|
graph.addNode(node.id, node);
|
|
4325
4665
|
nodesAdded++;
|
|
4326
4666
|
}
|
|
4327
|
-
const
|
|
4667
|
+
const relDockerfile = toPosix2(import_node_path28.default.relative(service.dir, dockerfilePath));
|
|
4668
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4669
|
+
graph,
|
|
4670
|
+
service.pkg.name,
|
|
4671
|
+
service.node.id,
|
|
4672
|
+
relDockerfile
|
|
4673
|
+
);
|
|
4674
|
+
nodesAdded += fn;
|
|
4675
|
+
edgesAdded += fe;
|
|
4676
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types19.EdgeType.RUNS_ON);
|
|
4328
4677
|
if (!graph.hasEdge(edgeId)) {
|
|
4329
4678
|
const edge = {
|
|
4330
4679
|
id: edgeId,
|
|
4331
|
-
source:
|
|
4680
|
+
source: fileNodeId,
|
|
4332
4681
|
target: node.id,
|
|
4333
|
-
type:
|
|
4334
|
-
provenance:
|
|
4335
|
-
confidence: (0,
|
|
4682
|
+
type: import_types19.EdgeType.RUNS_ON,
|
|
4683
|
+
provenance: import_types19.Provenance.EXTRACTED,
|
|
4684
|
+
confidence: (0, import_types19.confidenceForExtracted)("structural"),
|
|
4336
4685
|
evidence: {
|
|
4337
|
-
file:
|
|
4686
|
+
file: toPosix2(import_node_path28.default.relative(scanPath, dockerfilePath))
|
|
4338
4687
|
}
|
|
4339
4688
|
};
|
|
4340
4689
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4346,21 +4695,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4346
4695
|
|
|
4347
4696
|
// src/extract/infra/terraform.ts
|
|
4348
4697
|
init_cjs_shims();
|
|
4349
|
-
var
|
|
4350
|
-
var
|
|
4698
|
+
var import_node_fs16 = require("fs");
|
|
4699
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
4351
4700
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
4352
4701
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
4353
4702
|
if (depth > max) return [];
|
|
4354
4703
|
const out = [];
|
|
4355
|
-
const entries = await
|
|
4704
|
+
const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4356
4705
|
for (const entry2 of entries) {
|
|
4357
4706
|
if (entry2.isDirectory()) {
|
|
4358
4707
|
if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
|
|
4359
|
-
const child =
|
|
4708
|
+
const child = import_node_path29.default.join(start, entry2.name);
|
|
4360
4709
|
if (await isPythonVenvDir(child)) continue;
|
|
4361
4710
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
4362
4711
|
} else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
|
|
4363
|
-
out.push(
|
|
4712
|
+
out.push(import_node_path29.default.join(start, entry2.name));
|
|
4364
4713
|
}
|
|
4365
4714
|
}
|
|
4366
4715
|
return out;
|
|
@@ -4369,7 +4718,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4369
4718
|
let nodesAdded = 0;
|
|
4370
4719
|
const files = await walkTfFiles(scanPath);
|
|
4371
4720
|
for (const file of files) {
|
|
4372
|
-
const content = await
|
|
4721
|
+
const content = await import_node_fs16.promises.readFile(file, "utf8");
|
|
4373
4722
|
RESOURCE_RE.lastIndex = 0;
|
|
4374
4723
|
let m;
|
|
4375
4724
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -4387,8 +4736,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4387
4736
|
|
|
4388
4737
|
// src/extract/infra/k8s.ts
|
|
4389
4738
|
init_cjs_shims();
|
|
4390
|
-
var
|
|
4391
|
-
var
|
|
4739
|
+
var import_node_fs17 = require("fs");
|
|
4740
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
4392
4741
|
var import_yaml3 = require("yaml");
|
|
4393
4742
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
4394
4743
|
Service: "k8s-service",
|
|
@@ -4402,15 +4751,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
4402
4751
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
4403
4752
|
if (depth > max) return [];
|
|
4404
4753
|
const out = [];
|
|
4405
|
-
const entries = await
|
|
4754
|
+
const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4406
4755
|
for (const entry2 of entries) {
|
|
4407
4756
|
if (entry2.isDirectory()) {
|
|
4408
4757
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
4409
|
-
const child =
|
|
4758
|
+
const child = import_node_path30.default.join(start, entry2.name);
|
|
4410
4759
|
if (await isPythonVenvDir(child)) continue;
|
|
4411
4760
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
4412
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
4413
|
-
out.push(
|
|
4761
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path30.default.extname(entry2.name))) {
|
|
4762
|
+
out.push(import_node_path30.default.join(start, entry2.name));
|
|
4414
4763
|
}
|
|
4415
4764
|
}
|
|
4416
4765
|
return out;
|
|
@@ -4419,7 +4768,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
4419
4768
|
let nodesAdded = 0;
|
|
4420
4769
|
const files = await walkYamlFiles2(scanPath);
|
|
4421
4770
|
for (const file of files) {
|
|
4422
|
-
const content = await
|
|
4771
|
+
const content = await import_node_fs17.promises.readFile(file, "utf8");
|
|
4423
4772
|
let docs;
|
|
4424
4773
|
try {
|
|
4425
4774
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -4454,17 +4803,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
4454
4803
|
}
|
|
4455
4804
|
|
|
4456
4805
|
// src/extract/index.ts
|
|
4457
|
-
var
|
|
4806
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4458
4807
|
|
|
4459
4808
|
// src/extract/retire.ts
|
|
4460
4809
|
init_cjs_shims();
|
|
4461
|
-
var
|
|
4462
|
-
var
|
|
4463
|
-
var
|
|
4810
|
+
var import_node_fs18 = require("fs");
|
|
4811
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
4812
|
+
var import_types20 = require("@neat.is/types");
|
|
4464
4813
|
function dropOrphanedFileNodes(graph) {
|
|
4465
4814
|
const orphans = [];
|
|
4466
4815
|
graph.forEachNode((id, attrs) => {
|
|
4467
|
-
if (attrs.type !==
|
|
4816
|
+
if (attrs.type !== import_types20.NodeType.FileNode) return;
|
|
4468
4817
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
4469
4818
|
orphans.push(id);
|
|
4470
4819
|
}
|
|
@@ -4477,14 +4826,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4477
4826
|
const bases = [scanPath, ...serviceDirs];
|
|
4478
4827
|
graph.forEachEdge((id, attrs) => {
|
|
4479
4828
|
const edge = attrs;
|
|
4480
|
-
if (edge.provenance !==
|
|
4829
|
+
if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
|
|
4481
4830
|
const evidenceFile = edge.evidence?.file;
|
|
4482
4831
|
if (!evidenceFile) return;
|
|
4483
|
-
if (
|
|
4484
|
-
if (!(0,
|
|
4832
|
+
if (import_node_path31.default.isAbsolute(evidenceFile)) {
|
|
4833
|
+
if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
|
|
4485
4834
|
return;
|
|
4486
4835
|
}
|
|
4487
|
-
const found = bases.some((base) => (0,
|
|
4836
|
+
const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path31.default.join(base, evidenceFile)));
|
|
4488
4837
|
if (!found) toDrop.push(id);
|
|
4489
4838
|
});
|
|
4490
4839
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -4499,6 +4848,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4499
4848
|
const services = await discoverServices(scanPath);
|
|
4500
4849
|
const phase1Nodes = addServiceNodes(graph, services);
|
|
4501
4850
|
await addServiceAliases(graph, scanPath, services);
|
|
4851
|
+
const fileEnum = await addFiles(graph, services);
|
|
4852
|
+
const importGraph = await addImports(graph, services);
|
|
4502
4853
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
4503
4854
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
4504
4855
|
const phase4 = await addCallEdges(graph, services);
|
|
@@ -4522,7 +4873,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4522
4873
|
}
|
|
4523
4874
|
const droppedEntries = drainDroppedExtracted();
|
|
4524
4875
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
4525
|
-
const rejectedPath =
|
|
4876
|
+
const rejectedPath = import_node_path32.default.join(import_node_path32.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
4526
4877
|
try {
|
|
4527
4878
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
4528
4879
|
} catch (err) {
|
|
@@ -4532,8 +4883,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4532
4883
|
}
|
|
4533
4884
|
}
|
|
4534
4885
|
const result = {
|
|
4535
|
-
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
4536
|
-
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
4886
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
4887
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
4537
4888
|
frontiersPromoted,
|
|
4538
4889
|
extractionErrors: errorEntries.length,
|
|
4539
4890
|
errorEntries,
|
|
@@ -4556,9 +4907,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4556
4907
|
|
|
4557
4908
|
// src/persist.ts
|
|
4558
4909
|
init_cjs_shims();
|
|
4559
|
-
var
|
|
4560
|
-
var
|
|
4561
|
-
var
|
|
4910
|
+
var import_node_fs19 = require("fs");
|
|
4911
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4912
|
+
var import_types21 = require("@neat.is/types");
|
|
4562
4913
|
var SCHEMA_VERSION = 4;
|
|
4563
4914
|
function migrateV1ToV2(payload) {
|
|
4564
4915
|
const nodes = payload.graph.nodes;
|
|
@@ -4580,12 +4931,12 @@ function migrateV2ToV3(payload) {
|
|
|
4580
4931
|
for (const edge of edges) {
|
|
4581
4932
|
const attrs = edge.attributes;
|
|
4582
4933
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
4583
|
-
attrs.provenance =
|
|
4934
|
+
attrs.provenance = import_types21.Provenance.OBSERVED;
|
|
4584
4935
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
4585
4936
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
4586
4937
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
4587
4938
|
if (type && source && target) {
|
|
4588
|
-
const newId = (0,
|
|
4939
|
+
const newId = (0, import_types21.observedEdgeId)(source, target, type);
|
|
4589
4940
|
attrs.id = newId;
|
|
4590
4941
|
if (edge.key) edge.key = newId;
|
|
4591
4942
|
}
|
|
@@ -4594,7 +4945,7 @@ function migrateV2ToV3(payload) {
|
|
|
4594
4945
|
return { ...payload, schemaVersion: 3 };
|
|
4595
4946
|
}
|
|
4596
4947
|
async function ensureDir(filePath) {
|
|
4597
|
-
await
|
|
4948
|
+
await import_node_fs19.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
|
|
4598
4949
|
}
|
|
4599
4950
|
async function saveGraphToDisk(graph, outPath) {
|
|
4600
4951
|
await ensureDir(outPath);
|
|
@@ -4604,13 +4955,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
4604
4955
|
graph: graph.export()
|
|
4605
4956
|
};
|
|
4606
4957
|
const tmp = `${outPath}.tmp`;
|
|
4607
|
-
await
|
|
4608
|
-
await
|
|
4958
|
+
await import_node_fs19.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
4959
|
+
await import_node_fs19.promises.rename(tmp, outPath);
|
|
4609
4960
|
}
|
|
4610
4961
|
async function loadGraphFromDisk(graph, outPath) {
|
|
4611
4962
|
let raw;
|
|
4612
4963
|
try {
|
|
4613
|
-
raw = await
|
|
4964
|
+
raw = await import_node_fs19.promises.readFile(outPath, "utf8");
|
|
4614
4965
|
} catch (err) {
|
|
4615
4966
|
if (err.code === "ENOENT") return;
|
|
4616
4967
|
throw err;
|
|
@@ -4669,23 +5020,23 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
4669
5020
|
|
|
4670
5021
|
// src/projects.ts
|
|
4671
5022
|
init_cjs_shims();
|
|
4672
|
-
var
|
|
5023
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
4673
5024
|
function pathsForProject(project, baseDir) {
|
|
4674
5025
|
if (project === DEFAULT_PROJECT) {
|
|
4675
5026
|
return {
|
|
4676
|
-
snapshotPath:
|
|
4677
|
-
errorsPath:
|
|
4678
|
-
staleEventsPath:
|
|
4679
|
-
embeddingsCachePath:
|
|
4680
|
-
policyViolationsPath:
|
|
5027
|
+
snapshotPath: import_node_path34.default.join(baseDir, "graph.json"),
|
|
5028
|
+
errorsPath: import_node_path34.default.join(baseDir, "errors.ndjson"),
|
|
5029
|
+
staleEventsPath: import_node_path34.default.join(baseDir, "stale-events.ndjson"),
|
|
5030
|
+
embeddingsCachePath: import_node_path34.default.join(baseDir, "embeddings.json"),
|
|
5031
|
+
policyViolationsPath: import_node_path34.default.join(baseDir, "policy-violations.ndjson")
|
|
4681
5032
|
};
|
|
4682
5033
|
}
|
|
4683
5034
|
return {
|
|
4684
|
-
snapshotPath:
|
|
4685
|
-
errorsPath:
|
|
4686
|
-
staleEventsPath:
|
|
4687
|
-
embeddingsCachePath:
|
|
4688
|
-
policyViolationsPath:
|
|
5035
|
+
snapshotPath: import_node_path34.default.join(baseDir, `${project}.json`),
|
|
5036
|
+
errorsPath: import_node_path34.default.join(baseDir, `errors.${project}.ndjson`),
|
|
5037
|
+
staleEventsPath: import_node_path34.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
5038
|
+
embeddingsCachePath: import_node_path34.default.join(baseDir, `embeddings.${project}.json`),
|
|
5039
|
+
policyViolationsPath: import_node_path34.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
4689
5040
|
};
|
|
4690
5041
|
}
|
|
4691
5042
|
var Projects = class {
|
|
@@ -4723,11 +5074,326 @@ var Projects = class {
|
|
|
4723
5074
|
init_cjs_shims();
|
|
4724
5075
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
4725
5076
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4726
|
-
var
|
|
5077
|
+
var import_types24 = require("@neat.is/types");
|
|
5078
|
+
|
|
5079
|
+
// src/extend/index.ts
|
|
5080
|
+
init_cjs_shims();
|
|
5081
|
+
var import_node_fs21 = require("fs");
|
|
5082
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
5083
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
5084
|
+
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
5085
|
+
|
|
5086
|
+
// src/installers/package-manager.ts
|
|
5087
|
+
init_cjs_shims();
|
|
5088
|
+
var import_node_fs20 = require("fs");
|
|
5089
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5090
|
+
var import_node_child_process = require("child_process");
|
|
5091
|
+
var LOCKFILE_PRIORITY = [
|
|
5092
|
+
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
5093
|
+
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
5094
|
+
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
5095
|
+
{
|
|
5096
|
+
lockfile: "package-lock.json",
|
|
5097
|
+
pm: "npm",
|
|
5098
|
+
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
5099
|
+
}
|
|
5100
|
+
];
|
|
5101
|
+
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
5102
|
+
async function exists2(p) {
|
|
5103
|
+
try {
|
|
5104
|
+
await import_node_fs20.promises.access(p);
|
|
5105
|
+
return true;
|
|
5106
|
+
} catch {
|
|
5107
|
+
return false;
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
5110
|
+
async function detectPackageManager(serviceDir) {
|
|
5111
|
+
let dir = import_node_path35.default.resolve(serviceDir);
|
|
5112
|
+
const stops = /* @__PURE__ */ new Set();
|
|
5113
|
+
for (let i = 0; i < 64; i++) {
|
|
5114
|
+
if (stops.has(dir)) break;
|
|
5115
|
+
stops.add(dir);
|
|
5116
|
+
for (const candidate of LOCKFILE_PRIORITY) {
|
|
5117
|
+
const lockPath = import_node_path35.default.join(dir, candidate.lockfile);
|
|
5118
|
+
if (await exists2(lockPath)) {
|
|
5119
|
+
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
const parent = import_node_path35.default.dirname(dir);
|
|
5123
|
+
if (parent === dir) break;
|
|
5124
|
+
dir = parent;
|
|
5125
|
+
}
|
|
5126
|
+
return { pm: "npm", cwd: import_node_path35.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
5127
|
+
}
|
|
5128
|
+
async function runPackageManagerInstall(cmd) {
|
|
5129
|
+
return new Promise((resolve) => {
|
|
5130
|
+
const child = (0, import_node_child_process.spawn)(cmd.pm, cmd.args, {
|
|
5131
|
+
cwd: cmd.cwd,
|
|
5132
|
+
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
5133
|
+
env: process.env,
|
|
5134
|
+
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
5135
|
+
// exits before install finishes. Cross-platform-safe.
|
|
5136
|
+
shell: false,
|
|
5137
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
5138
|
+
});
|
|
5139
|
+
let stderr = "";
|
|
5140
|
+
child.stderr?.on("data", (chunk) => {
|
|
5141
|
+
stderr += chunk.toString("utf8");
|
|
5142
|
+
});
|
|
5143
|
+
child.on("error", (err) => {
|
|
5144
|
+
resolve({
|
|
5145
|
+
pm: cmd.pm,
|
|
5146
|
+
cwd: cmd.cwd,
|
|
5147
|
+
args: cmd.args,
|
|
5148
|
+
exitCode: 127,
|
|
5149
|
+
stderr: stderr + `
|
|
5150
|
+
${err.message}`
|
|
5151
|
+
});
|
|
5152
|
+
});
|
|
5153
|
+
child.on("close", (code) => {
|
|
5154
|
+
resolve({
|
|
5155
|
+
pm: cmd.pm,
|
|
5156
|
+
cwd: cmd.cwd,
|
|
5157
|
+
args: cmd.args,
|
|
5158
|
+
exitCode: code ?? 1,
|
|
5159
|
+
stderr: stderr.trim()
|
|
5160
|
+
});
|
|
5161
|
+
});
|
|
5162
|
+
});
|
|
5163
|
+
}
|
|
5164
|
+
|
|
5165
|
+
// src/extend/index.ts
|
|
5166
|
+
async function fileExists2(p) {
|
|
5167
|
+
try {
|
|
5168
|
+
await import_node_fs21.promises.access(p);
|
|
5169
|
+
return true;
|
|
5170
|
+
} catch {
|
|
5171
|
+
return false;
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5174
|
+
async function readPackageJson(scanPath) {
|
|
5175
|
+
const pkgPath = import_node_path36.default.join(scanPath, "package.json");
|
|
5176
|
+
const raw = await import_node_fs21.promises.readFile(pkgPath, "utf8");
|
|
5177
|
+
return JSON.parse(raw);
|
|
5178
|
+
}
|
|
5179
|
+
async function findHookFiles(scanPath) {
|
|
5180
|
+
const entries = await import_node_fs21.promises.readdir(scanPath);
|
|
5181
|
+
return entries.filter(
|
|
5182
|
+
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
5183
|
+
).sort();
|
|
5184
|
+
}
|
|
5185
|
+
function extendLogPath() {
|
|
5186
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path36.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
5187
|
+
}
|
|
5188
|
+
async function appendExtendLog(entry2) {
|
|
5189
|
+
const logPath = extendLogPath();
|
|
5190
|
+
await import_node_fs21.promises.mkdir(import_node_path36.default.dirname(logPath), { recursive: true });
|
|
5191
|
+
await import_node_fs21.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
|
|
5192
|
+
}
|
|
5193
|
+
function splicedContent(fileContent, snippet2) {
|
|
5194
|
+
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
5195
|
+
return fileContent.replace("__INSTRUMENTATION_BLOCK__", `${snippet2}
|
|
5196
|
+
__INSTRUMENTATION_BLOCK__`);
|
|
5197
|
+
}
|
|
5198
|
+
const lines = fileContent.split("\n");
|
|
5199
|
+
let lastPushIdx = -1;
|
|
5200
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5201
|
+
if (lines[i].includes("instrumentations.push(")) lastPushIdx = i;
|
|
5202
|
+
}
|
|
5203
|
+
if (lastPushIdx >= 0) {
|
|
5204
|
+
lines.splice(lastPushIdx + 1, 0, snippet2);
|
|
5205
|
+
return lines.join("\n");
|
|
5206
|
+
}
|
|
5207
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5208
|
+
if (lines[i].includes("new NodeSDK(")) {
|
|
5209
|
+
lines.splice(i, 0, snippet2);
|
|
5210
|
+
return lines.join("\n");
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
return null;
|
|
5214
|
+
}
|
|
5215
|
+
async function listUninstrumented(ctx) {
|
|
5216
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5217
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5218
|
+
const results = [];
|
|
5219
|
+
for (const [library, installedVersion] of Object.entries(allDeps)) {
|
|
5220
|
+
const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
5221
|
+
if (!entry2) continue;
|
|
5222
|
+
if (entry2.coverage === "bundled" || entry2.coverage === "http-only") continue;
|
|
5223
|
+
results.push({
|
|
5224
|
+
library,
|
|
5225
|
+
coverage: entry2.coverage,
|
|
5226
|
+
installedVersion,
|
|
5227
|
+
instrumentation_package: entry2.instrumentation_package,
|
|
5228
|
+
package_version: entry2.package_version,
|
|
5229
|
+
registration: entry2.registration,
|
|
5230
|
+
notes: entry2.notes
|
|
5231
|
+
});
|
|
5232
|
+
}
|
|
5233
|
+
return results;
|
|
5234
|
+
}
|
|
5235
|
+
function lookupInstrumentation(library, installedVersion) {
|
|
5236
|
+
const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
5237
|
+
if (!entry2) return null;
|
|
5238
|
+
return {
|
|
5239
|
+
library: entry2.library,
|
|
5240
|
+
coverage: entry2.coverage,
|
|
5241
|
+
instrumentation_package: entry2.instrumentation_package,
|
|
5242
|
+
package_version: entry2.package_version,
|
|
5243
|
+
registration: entry2.registration,
|
|
5244
|
+
notes: entry2.notes
|
|
5245
|
+
};
|
|
5246
|
+
}
|
|
5247
|
+
async function describeProjectInstrumentation(ctx) {
|
|
5248
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5249
|
+
const envNeat = await fileExists2(import_node_path36.default.join(ctx.scanPath, ".env.neat"));
|
|
5250
|
+
const registryInstrPackages = new Set(
|
|
5251
|
+
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
5252
|
+
);
|
|
5253
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5254
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5255
|
+
const installedDeps = {};
|
|
5256
|
+
for (const [key, version] of Object.entries(allDeps)) {
|
|
5257
|
+
if (key.startsWith("@opentelemetry/") || registryInstrPackages.has(key)) {
|
|
5258
|
+
installedDeps[key] = version;
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5261
|
+
return { hookFiles, envNeat, installedDeps };
|
|
5262
|
+
}
|
|
5263
|
+
async function applyExtension(ctx, args, options) {
|
|
5264
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5265
|
+
if (hookFiles.length === 0) {
|
|
5266
|
+
throw new Error(
|
|
5267
|
+
`No instrumentation hook files found in ${ctx.scanPath}. Run \`neat init\` first.`
|
|
5268
|
+
);
|
|
5269
|
+
}
|
|
5270
|
+
for (const file of hookFiles) {
|
|
5271
|
+
const content = await import_node_fs21.promises.readFile(import_node_path36.default.join(ctx.scanPath, file), "utf8");
|
|
5272
|
+
if (content.includes(args.registration_snippet)) {
|
|
5273
|
+
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
const primaryFile = hookFiles[0];
|
|
5277
|
+
const primaryPath = import_node_path36.default.join(ctx.scanPath, primaryFile);
|
|
5278
|
+
const filesTouched = [];
|
|
5279
|
+
const depsAdded = [];
|
|
5280
|
+
const pkgPath = import_node_path36.default.join(ctx.scanPath, "package.json");
|
|
5281
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5282
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5283
|
+
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
5284
|
+
await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5285
|
+
filesTouched.push("package.json");
|
|
5286
|
+
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
5287
|
+
}
|
|
5288
|
+
const hookContent = await import_node_fs21.promises.readFile(primaryPath, "utf8");
|
|
5289
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5290
|
+
if (!patched) {
|
|
5291
|
+
throw new Error(
|
|
5292
|
+
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
5293
|
+
);
|
|
5294
|
+
}
|
|
5295
|
+
await import_node_fs21.promises.writeFile(primaryPath, patched, "utf8");
|
|
5296
|
+
filesTouched.push(primaryFile);
|
|
5297
|
+
const cmd = await detectPackageManager(ctx.scanPath);
|
|
5298
|
+
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
5299
|
+
const install = await installer(cmd);
|
|
5300
|
+
const installOutput = install.exitCode === 0 ? `${cmd.pm} install succeeded` : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`;
|
|
5301
|
+
await appendExtendLog({
|
|
5302
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5303
|
+
project: ctx.project,
|
|
5304
|
+
library: args.library,
|
|
5305
|
+
instrumentation_package: args.instrumentation_package,
|
|
5306
|
+
version: args.version,
|
|
5307
|
+
registration_snippet: args.registration_snippet,
|
|
5308
|
+
filesTouched,
|
|
5309
|
+
depsAdded,
|
|
5310
|
+
installOutput
|
|
5311
|
+
});
|
|
5312
|
+
return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false };
|
|
5313
|
+
}
|
|
5314
|
+
async function dryRunExtension(ctx, args) {
|
|
5315
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5316
|
+
if (hookFiles.length === 0) {
|
|
5317
|
+
return {
|
|
5318
|
+
library: args.library,
|
|
5319
|
+
filesTouched: [],
|
|
5320
|
+
depsToAdd: [],
|
|
5321
|
+
packageJsonPatch: {},
|
|
5322
|
+
templatePatch: "No hook files found. Run 'neat init' first."
|
|
5323
|
+
};
|
|
5324
|
+
}
|
|
5325
|
+
for (const file of hookFiles) {
|
|
5326
|
+
const content = await import_node_fs21.promises.readFile(import_node_path36.default.join(ctx.scanPath, file), "utf8");
|
|
5327
|
+
if (content.includes(args.registration_snippet)) {
|
|
5328
|
+
return {
|
|
5329
|
+
library: args.library,
|
|
5330
|
+
filesTouched: [],
|
|
5331
|
+
depsToAdd: [],
|
|
5332
|
+
packageJsonPatch: {},
|
|
5333
|
+
templatePatch: "Already applied \u2014 no changes would be made."
|
|
5334
|
+
};
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
const primaryFile = hookFiles[0];
|
|
5338
|
+
const filesTouched = [];
|
|
5339
|
+
const depsToAdd = [];
|
|
5340
|
+
let packageJsonPatch = {};
|
|
5341
|
+
let templatePatch = "";
|
|
5342
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5343
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5344
|
+
packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } };
|
|
5345
|
+
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
5346
|
+
filesTouched.push("package.json");
|
|
5347
|
+
}
|
|
5348
|
+
const hookContent = await import_node_fs21.promises.readFile(import_node_path36.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
5349
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5350
|
+
if (patched) {
|
|
5351
|
+
filesTouched.push(primaryFile);
|
|
5352
|
+
templatePatch = `+ ${args.registration_snippet}`;
|
|
5353
|
+
} else {
|
|
5354
|
+
templatePatch = "Could not find insertion point in hook file.";
|
|
5355
|
+
}
|
|
5356
|
+
return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch };
|
|
5357
|
+
}
|
|
5358
|
+
async function rollbackExtension(ctx, args) {
|
|
5359
|
+
const logPath = extendLogPath();
|
|
5360
|
+
if (!await fileExists2(logPath)) {
|
|
5361
|
+
return { undone: false, message: "no apply found for library" };
|
|
5362
|
+
}
|
|
5363
|
+
const raw = await import_node_fs21.promises.readFile(logPath, "utf8");
|
|
5364
|
+
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
5365
|
+
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
5366
|
+
if (!match) {
|
|
5367
|
+
return { undone: false, message: "no apply found for library" };
|
|
5368
|
+
}
|
|
5369
|
+
const pkgPath = import_node_path36.default.join(ctx.scanPath, "package.json");
|
|
5370
|
+
if (await fileExists2(pkgPath)) {
|
|
5371
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5372
|
+
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
5373
|
+
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
5374
|
+
pkg.dependencies = rest;
|
|
5375
|
+
await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5376
|
+
}
|
|
5377
|
+
}
|
|
5378
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5379
|
+
for (const file of hookFiles) {
|
|
5380
|
+
const filePath = import_node_path36.default.join(ctx.scanPath, file);
|
|
5381
|
+
const content = await import_node_fs21.promises.readFile(filePath, "utf8");
|
|
5382
|
+
if (content.includes(match.registration_snippet)) {
|
|
5383
|
+
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
5384
|
+
await import_node_fs21.promises.writeFile(filePath, filtered, "utf8");
|
|
5385
|
+
break;
|
|
5386
|
+
}
|
|
5387
|
+
}
|
|
5388
|
+
return {
|
|
5389
|
+
undone: true,
|
|
5390
|
+
message: `rolled back ${match.library} (${match.instrumentation_package})`
|
|
5391
|
+
};
|
|
5392
|
+
}
|
|
4727
5393
|
|
|
4728
5394
|
// src/divergences.ts
|
|
4729
5395
|
init_cjs_shims();
|
|
4730
|
-
var
|
|
5396
|
+
var import_types22 = require("@neat.is/types");
|
|
4731
5397
|
function bucketKey(source, target, type) {
|
|
4732
5398
|
return `${type}|${source}|${target}`;
|
|
4733
5399
|
}
|
|
@@ -4735,22 +5401,22 @@ function bucketEdges(graph) {
|
|
|
4735
5401
|
const buckets = /* @__PURE__ */ new Map();
|
|
4736
5402
|
graph.forEachEdge((id, attrs) => {
|
|
4737
5403
|
const e = attrs;
|
|
4738
|
-
const parsed = (0,
|
|
5404
|
+
const parsed = (0, import_types22.parseEdgeId)(id);
|
|
4739
5405
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
4740
5406
|
const key = bucketKey(e.source, e.target, e.type);
|
|
4741
5407
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
4742
5408
|
switch (provenance) {
|
|
4743
|
-
case
|
|
5409
|
+
case import_types22.Provenance.EXTRACTED:
|
|
4744
5410
|
cur.extracted = e;
|
|
4745
5411
|
break;
|
|
4746
|
-
case
|
|
5412
|
+
case import_types22.Provenance.OBSERVED:
|
|
4747
5413
|
cur.observed = e;
|
|
4748
5414
|
break;
|
|
4749
|
-
case
|
|
5415
|
+
case import_types22.Provenance.INFERRED:
|
|
4750
5416
|
cur.inferred = e;
|
|
4751
5417
|
break;
|
|
4752
5418
|
default:
|
|
4753
|
-
if (e.provenance ===
|
|
5419
|
+
if (e.provenance === import_types22.Provenance.STALE) cur.stale = e;
|
|
4754
5420
|
}
|
|
4755
5421
|
buckets.set(key, cur);
|
|
4756
5422
|
});
|
|
@@ -4759,7 +5425,7 @@ function bucketEdges(graph) {
|
|
|
4759
5425
|
function nodeIsFrontier(graph, nodeId) {
|
|
4760
5426
|
if (!graph.hasNode(nodeId)) return false;
|
|
4761
5427
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4762
|
-
return attrs.type ===
|
|
5428
|
+
return attrs.type === import_types22.NodeType.FrontierNode;
|
|
4763
5429
|
}
|
|
4764
5430
|
function clampConfidence(n) {
|
|
4765
5431
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -4780,7 +5446,7 @@ function gradedConfidence(edge) {
|
|
|
4780
5446
|
}
|
|
4781
5447
|
function detectMissingDivergences(graph, bucket) {
|
|
4782
5448
|
const out = [];
|
|
4783
|
-
if (bucket.type ===
|
|
5449
|
+
if (bucket.type === import_types22.EdgeType.CONTAINS) return out;
|
|
4784
5450
|
if (bucket.extracted && !bucket.observed) {
|
|
4785
5451
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
4786
5452
|
out.push({
|
|
@@ -4821,7 +5487,7 @@ function declaredHostFor(svc) {
|
|
|
4821
5487
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4822
5488
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4823
5489
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4824
|
-
if (e.type ===
|
|
5490
|
+
if (e.type === import_types22.EdgeType.CONFIGURED_BY && e.provenance === import_types22.Provenance.EXTRACTED) {
|
|
4825
5491
|
return true;
|
|
4826
5492
|
}
|
|
4827
5493
|
}
|
|
@@ -4834,10 +5500,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4834
5500
|
const out = [];
|
|
4835
5501
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4836
5502
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4837
|
-
if (edge.type !==
|
|
4838
|
-
if (edge.provenance !==
|
|
5503
|
+
if (edge.type !== import_types22.EdgeType.CONNECTS_TO) continue;
|
|
5504
|
+
if (edge.provenance !== import_types22.Provenance.OBSERVED) continue;
|
|
4839
5505
|
const target = graph.getNodeAttributes(edge.target);
|
|
4840
|
-
if (target.type !==
|
|
5506
|
+
if (target.type !== import_types22.NodeType.DatabaseNode) continue;
|
|
4841
5507
|
const observedHost = target.host?.trim();
|
|
4842
5508
|
if (!observedHost) continue;
|
|
4843
5509
|
if (observedHost === declaredHost) continue;
|
|
@@ -4859,10 +5525,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4859
5525
|
const deps = svc.dependencies ?? {};
|
|
4860
5526
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4861
5527
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4862
|
-
if (edge.type !==
|
|
4863
|
-
if (edge.provenance !==
|
|
5528
|
+
if (edge.type !== import_types22.EdgeType.CONNECTS_TO) continue;
|
|
5529
|
+
if (edge.provenance !== import_types22.Provenance.OBSERVED) continue;
|
|
4864
5530
|
const target = graph.getNodeAttributes(edge.target);
|
|
4865
|
-
if (target.type !==
|
|
5531
|
+
if (target.type !== import_types22.NodeType.DatabaseNode) continue;
|
|
4866
5532
|
for (const pair of compatPairs()) {
|
|
4867
5533
|
if (pair.engine !== target.engine) continue;
|
|
4868
5534
|
const declared = deps[pair.driver];
|
|
@@ -4923,7 +5589,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4923
5589
|
}
|
|
4924
5590
|
graph.forEachNode((nodeId, attrs) => {
|
|
4925
5591
|
const n = attrs;
|
|
4926
|
-
if (n.type !==
|
|
5592
|
+
if (n.type !== import_types22.NodeType.ServiceNode) return;
|
|
4927
5593
|
const svc = n;
|
|
4928
5594
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4929
5595
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -4956,7 +5622,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4956
5622
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4957
5623
|
return a.target.localeCompare(b.target);
|
|
4958
5624
|
});
|
|
4959
|
-
return
|
|
5625
|
+
return import_types22.DivergenceResultSchema.parse({
|
|
4960
5626
|
divergences: filtered,
|
|
4961
5627
|
totalAffected: filtered.length,
|
|
4962
5628
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4965,7 +5631,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4965
5631
|
|
|
4966
5632
|
// src/diff.ts
|
|
4967
5633
|
init_cjs_shims();
|
|
4968
|
-
var
|
|
5634
|
+
var import_node_fs22 = require("fs");
|
|
4969
5635
|
async function loadSnapshotForDiff(target) {
|
|
4970
5636
|
if (/^https?:\/\//i.test(target)) {
|
|
4971
5637
|
const res = await fetch(target);
|
|
@@ -4974,7 +5640,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
4974
5640
|
}
|
|
4975
5641
|
return await res.json();
|
|
4976
5642
|
}
|
|
4977
|
-
const raw = await
|
|
5643
|
+
const raw = await import_node_fs22.promises.readFile(target, "utf8");
|
|
4978
5644
|
return JSON.parse(raw);
|
|
4979
5645
|
}
|
|
4980
5646
|
function indexEntries(entries) {
|
|
@@ -5042,25 +5708,25 @@ function canonicalJson(value) {
|
|
|
5042
5708
|
|
|
5043
5709
|
// src/registry.ts
|
|
5044
5710
|
init_cjs_shims();
|
|
5045
|
-
var
|
|
5046
|
-
var
|
|
5047
|
-
var
|
|
5048
|
-
var
|
|
5711
|
+
var import_node_fs23 = require("fs");
|
|
5712
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
5713
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
5714
|
+
var import_types23 = require("@neat.is/types");
|
|
5049
5715
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
5050
5716
|
var LOCK_RETRY_MS = 50;
|
|
5051
5717
|
function neatHome() {
|
|
5052
5718
|
const override = process.env.NEAT_HOME;
|
|
5053
|
-
if (override && override.length > 0) return
|
|
5054
|
-
return
|
|
5719
|
+
if (override && override.length > 0) return import_node_path37.default.resolve(override);
|
|
5720
|
+
return import_node_path37.default.join(import_node_os3.default.homedir(), ".neat");
|
|
5055
5721
|
}
|
|
5056
5722
|
function registryPath() {
|
|
5057
|
-
return
|
|
5723
|
+
return import_node_path37.default.join(neatHome(), "projects.json");
|
|
5058
5724
|
}
|
|
5059
5725
|
function registryLockPath() {
|
|
5060
|
-
return
|
|
5726
|
+
return import_node_path37.default.join(neatHome(), "projects.json.lock");
|
|
5061
5727
|
}
|
|
5062
5728
|
function daemonPidPath() {
|
|
5063
|
-
return
|
|
5729
|
+
return import_node_path37.default.join(neatHome(), "neatd.pid");
|
|
5064
5730
|
}
|
|
5065
5731
|
function isPidAliveDefault(pid) {
|
|
5066
5732
|
try {
|
|
@@ -5072,7 +5738,7 @@ function isPidAliveDefault(pid) {
|
|
|
5072
5738
|
}
|
|
5073
5739
|
async function readPidFile(file) {
|
|
5074
5740
|
try {
|
|
5075
|
-
const raw = await
|
|
5741
|
+
const raw = await import_node_fs23.promises.readFile(file, "utf8");
|
|
5076
5742
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
5077
5743
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
5078
5744
|
} catch {
|
|
@@ -5120,24 +5786,24 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
5120
5786
|
}
|
|
5121
5787
|
}
|
|
5122
5788
|
async function writeAtomically(target, contents) {
|
|
5123
|
-
await
|
|
5789
|
+
await import_node_fs23.promises.mkdir(import_node_path37.default.dirname(target), { recursive: true });
|
|
5124
5790
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
5125
|
-
const fd = await
|
|
5791
|
+
const fd = await import_node_fs23.promises.open(tmp, "w");
|
|
5126
5792
|
try {
|
|
5127
5793
|
await fd.writeFile(contents, "utf8");
|
|
5128
5794
|
await fd.sync();
|
|
5129
5795
|
} finally {
|
|
5130
5796
|
await fd.close();
|
|
5131
5797
|
}
|
|
5132
|
-
await
|
|
5798
|
+
await import_node_fs23.promises.rename(tmp, target);
|
|
5133
5799
|
}
|
|
5134
5800
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
5135
5801
|
const deadline = Date.now() + timeoutMs;
|
|
5136
|
-
await
|
|
5802
|
+
await import_node_fs23.promises.mkdir(import_node_path37.default.dirname(lockPath), { recursive: true });
|
|
5137
5803
|
let probedHolder = false;
|
|
5138
5804
|
while (true) {
|
|
5139
5805
|
try {
|
|
5140
|
-
const fd = await
|
|
5806
|
+
const fd = await import_node_fs23.promises.open(lockPath, "wx");
|
|
5141
5807
|
try {
|
|
5142
5808
|
await fd.writeFile(`${process.pid}
|
|
5143
5809
|
`, "utf8");
|
|
@@ -5162,7 +5828,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
5162
5828
|
}
|
|
5163
5829
|
}
|
|
5164
5830
|
async function releaseLock(lockPath) {
|
|
5165
|
-
await
|
|
5831
|
+
await import_node_fs23.promises.unlink(lockPath).catch(() => {
|
|
5166
5832
|
});
|
|
5167
5833
|
}
|
|
5168
5834
|
async function withLock(fn) {
|
|
@@ -5178,7 +5844,7 @@ async function readRegistry() {
|
|
|
5178
5844
|
const file = registryPath();
|
|
5179
5845
|
let raw;
|
|
5180
5846
|
try {
|
|
5181
|
-
raw = await
|
|
5847
|
+
raw = await import_node_fs23.promises.readFile(file, "utf8");
|
|
5182
5848
|
} catch (err) {
|
|
5183
5849
|
if (err.code === "ENOENT") {
|
|
5184
5850
|
return { version: 1, projects: [] };
|
|
@@ -5186,10 +5852,10 @@ async function readRegistry() {
|
|
|
5186
5852
|
throw err;
|
|
5187
5853
|
}
|
|
5188
5854
|
const parsed = JSON.parse(raw);
|
|
5189
|
-
return
|
|
5855
|
+
return import_types23.RegistryFileSchema.parse(parsed);
|
|
5190
5856
|
}
|
|
5191
5857
|
async function writeRegistry(reg) {
|
|
5192
|
-
const validated =
|
|
5858
|
+
const validated = import_types23.RegistryFileSchema.parse(reg);
|
|
5193
5859
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
5194
5860
|
}
|
|
5195
5861
|
async function getProject(name) {
|
|
@@ -5411,11 +6077,11 @@ function registerRoutes(scope, ctx) {
|
|
|
5411
6077
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
5412
6078
|
const parsed = [];
|
|
5413
6079
|
for (const c of candidates) {
|
|
5414
|
-
const r =
|
|
6080
|
+
const r = import_types24.DivergenceTypeSchema.safeParse(c);
|
|
5415
6081
|
if (!r.success) {
|
|
5416
6082
|
return reply.code(400).send({
|
|
5417
6083
|
error: `unknown divergence type "${c}"`,
|
|
5418
|
-
allowed:
|
|
6084
|
+
allowed: import_types24.DivergenceTypeSchema.options
|
|
5419
6085
|
});
|
|
5420
6086
|
}
|
|
5421
6087
|
parsed.push(r.data);
|
|
@@ -5628,7 +6294,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5628
6294
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
5629
6295
|
let violations = await log.readAll();
|
|
5630
6296
|
if (req2.query.severity) {
|
|
5631
|
-
const sev =
|
|
6297
|
+
const sev = import_types24.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
5632
6298
|
if (!sev.success) {
|
|
5633
6299
|
return reply.code(400).send({
|
|
5634
6300
|
error: "invalid severity",
|
|
@@ -5645,7 +6311,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5645
6311
|
scope.post("/policies/check", async (req2, reply) => {
|
|
5646
6312
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
5647
6313
|
if (!proj) return;
|
|
5648
|
-
const parsed =
|
|
6314
|
+
const parsed = import_types24.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
5649
6315
|
if (!parsed.success) {
|
|
5650
6316
|
return reply.code(400).send({
|
|
5651
6317
|
error: "invalid /policies/check body",
|
|
@@ -5678,6 +6344,105 @@ function registerRoutes(scope, ctx) {
|
|
|
5678
6344
|
violations
|
|
5679
6345
|
};
|
|
5680
6346
|
});
|
|
6347
|
+
scope.get("/extend/list-uninstrumented", async (req2, reply) => {
|
|
6348
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6349
|
+
if (!proj) return;
|
|
6350
|
+
if (!proj.scanPath) {
|
|
6351
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6352
|
+
}
|
|
6353
|
+
try {
|
|
6354
|
+
const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
6355
|
+
return { libraries: results };
|
|
6356
|
+
} catch (err) {
|
|
6357
|
+
return reply.code(500).send({ error: err.message });
|
|
6358
|
+
}
|
|
6359
|
+
});
|
|
6360
|
+
scope.get("/extend/lookup", async (req2, reply) => {
|
|
6361
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6362
|
+
if (!proj) return;
|
|
6363
|
+
const { library, version } = req2.query;
|
|
6364
|
+
if (!library) {
|
|
6365
|
+
return reply.code(400).send({ error: "query parameter `library` is required" });
|
|
6366
|
+
}
|
|
6367
|
+
const result = lookupInstrumentation(library, version);
|
|
6368
|
+
if (!result) {
|
|
6369
|
+
return reply.code(404).send({ error: "library not found in registry", library });
|
|
6370
|
+
}
|
|
6371
|
+
return result;
|
|
6372
|
+
});
|
|
6373
|
+
scope.get("/extend/describe", async (req2, reply) => {
|
|
6374
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6375
|
+
if (!proj) return;
|
|
6376
|
+
if (!proj.scanPath) {
|
|
6377
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6378
|
+
}
|
|
6379
|
+
try {
|
|
6380
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
6381
|
+
return state;
|
|
6382
|
+
} catch (err) {
|
|
6383
|
+
return reply.code(500).send({ error: err.message });
|
|
6384
|
+
}
|
|
6385
|
+
});
|
|
6386
|
+
scope.post("/extend/apply", async (req2, reply) => {
|
|
6387
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6388
|
+
if (!proj) return;
|
|
6389
|
+
if (!proj.scanPath) {
|
|
6390
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6391
|
+
}
|
|
6392
|
+
const { library, instrumentation_package, version, registration_snippet } = req2.body ?? {};
|
|
6393
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6394
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6395
|
+
}
|
|
6396
|
+
try {
|
|
6397
|
+
const result = await applyExtension(
|
|
6398
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6399
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6400
|
+
);
|
|
6401
|
+
return result;
|
|
6402
|
+
} catch (err) {
|
|
6403
|
+
return reply.code(500).send({ error: err.message });
|
|
6404
|
+
}
|
|
6405
|
+
});
|
|
6406
|
+
scope.post("/extend/dry-run", async (req2, reply) => {
|
|
6407
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6408
|
+
if (!proj) return;
|
|
6409
|
+
if (!proj.scanPath) {
|
|
6410
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6411
|
+
}
|
|
6412
|
+
const { library, instrumentation_package, version, registration_snippet } = req2.body ?? {};
|
|
6413
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6414
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6415
|
+
}
|
|
6416
|
+
try {
|
|
6417
|
+
const result = await dryRunExtension(
|
|
6418
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6419
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6420
|
+
);
|
|
6421
|
+
return result;
|
|
6422
|
+
} catch (err) {
|
|
6423
|
+
return reply.code(500).send({ error: err.message });
|
|
6424
|
+
}
|
|
6425
|
+
});
|
|
6426
|
+
scope.post("/extend/rollback", async (req2, reply) => {
|
|
6427
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6428
|
+
if (!proj) return;
|
|
6429
|
+
if (!proj.scanPath) {
|
|
6430
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6431
|
+
}
|
|
6432
|
+
const { library } = req2.body ?? {};
|
|
6433
|
+
if (!library) {
|
|
6434
|
+
return reply.code(400).send({ error: "body must include library" });
|
|
6435
|
+
}
|
|
6436
|
+
try {
|
|
6437
|
+
const result = await rollbackExtension(
|
|
6438
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6439
|
+
{ library }
|
|
6440
|
+
);
|
|
6441
|
+
return result;
|
|
6442
|
+
} catch (err) {
|
|
6443
|
+
return reply.code(500).send({ error: err.message });
|
|
6444
|
+
}
|
|
6445
|
+
});
|
|
5681
6446
|
}
|
|
5682
6447
|
async function buildApi(opts) {
|
|
5683
6448
|
const app = (0, import_fastify.default)({ logger: false });
|
|
@@ -5788,8 +6553,8 @@ init_auth();
|
|
|
5788
6553
|
|
|
5789
6554
|
// src/unrouted.ts
|
|
5790
6555
|
init_cjs_shims();
|
|
5791
|
-
var
|
|
5792
|
-
var
|
|
6556
|
+
var import_node_fs24 = require("fs");
|
|
6557
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
5793
6558
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
5794
6559
|
return {
|
|
5795
6560
|
timestamp: now.toISOString(),
|
|
@@ -5799,21 +6564,21 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
5799
6564
|
};
|
|
5800
6565
|
}
|
|
5801
6566
|
async function appendUnroutedSpan(neatHome3, record) {
|
|
5802
|
-
const target =
|
|
5803
|
-
await
|
|
5804
|
-
await
|
|
6567
|
+
const target = import_node_path40.default.join(neatHome3, "errors.ndjson");
|
|
6568
|
+
await import_node_fs24.promises.mkdir(neatHome3, { recursive: true });
|
|
6569
|
+
await import_node_fs24.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
5805
6570
|
}
|
|
5806
6571
|
function unroutedErrorsPath(neatHome3) {
|
|
5807
|
-
return
|
|
6572
|
+
return import_node_path40.default.join(neatHome3, "errors.ndjson");
|
|
5808
6573
|
}
|
|
5809
6574
|
|
|
5810
6575
|
// src/daemon.ts
|
|
5811
6576
|
function neatHomeFor(opts) {
|
|
5812
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
6577
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path41.default.resolve(opts.neatHome);
|
|
5813
6578
|
const env = process.env.NEAT_HOME;
|
|
5814
|
-
if (env && env.length > 0) return
|
|
6579
|
+
if (env && env.length > 0) return import_node_path41.default.resolve(env);
|
|
5815
6580
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
5816
|
-
return
|
|
6581
|
+
return import_node_path41.default.join(home, ".neat");
|
|
5817
6582
|
}
|
|
5818
6583
|
function routeSpanToProject(serviceName, projects) {
|
|
5819
6584
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -5848,9 +6613,9 @@ function isTokenContained(needle, haystack) {
|
|
|
5848
6613
|
return tokens.includes(needle);
|
|
5849
6614
|
}
|
|
5850
6615
|
async function bootstrapProject(entry2) {
|
|
5851
|
-
const paths = pathsForProject(entry2.name,
|
|
6616
|
+
const paths = pathsForProject(entry2.name, import_node_path41.default.join(entry2.path, "neat-out"));
|
|
5852
6617
|
try {
|
|
5853
|
-
const stat = await
|
|
6618
|
+
const stat = await import_node_fs25.promises.stat(entry2.path);
|
|
5854
6619
|
if (!stat.isDirectory()) {
|
|
5855
6620
|
throw new Error(`registered path ${entry2.path} is not a directory`);
|
|
5856
6621
|
}
|
|
@@ -5916,13 +6681,13 @@ async function startDaemon(opts = {}) {
|
|
|
5916
6681
|
const home = neatHomeFor(opts);
|
|
5917
6682
|
const regPath = registryPath();
|
|
5918
6683
|
try {
|
|
5919
|
-
await
|
|
6684
|
+
await import_node_fs25.promises.access(regPath);
|
|
5920
6685
|
} catch {
|
|
5921
6686
|
throw new Error(
|
|
5922
6687
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
5923
6688
|
);
|
|
5924
6689
|
}
|
|
5925
|
-
const pidPath =
|
|
6690
|
+
const pidPath = import_node_path41.default.join(home, "neatd.pid");
|
|
5926
6691
|
await writeAtomically(pidPath, `${process.pid}
|
|
5927
6692
|
`);
|
|
5928
6693
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -6080,7 +6845,7 @@ async function startDaemon(opts = {}) {
|
|
|
6080
6845
|
}
|
|
6081
6846
|
if (restApp) await restApp.close().catch(() => {
|
|
6082
6847
|
});
|
|
6083
|
-
await
|
|
6848
|
+
await import_node_fs25.promises.unlink(pidPath).catch(() => {
|
|
6084
6849
|
});
|
|
6085
6850
|
throw new Error(
|
|
6086
6851
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -6186,7 +6951,7 @@ async function startDaemon(opts = {}) {
|
|
|
6186
6951
|
});
|
|
6187
6952
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
6188
6953
|
});
|
|
6189
|
-
await
|
|
6954
|
+
await import_node_fs25.promises.unlink(pidPath).catch(() => {
|
|
6190
6955
|
});
|
|
6191
6956
|
throw new Error(
|
|
6192
6957
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -6232,9 +6997,9 @@ async function startDaemon(opts = {}) {
|
|
|
6232
6997
|
let registryWatcher = null;
|
|
6233
6998
|
let reloadTimer = null;
|
|
6234
6999
|
try {
|
|
6235
|
-
const regDir =
|
|
6236
|
-
const regBase =
|
|
6237
|
-
registryWatcher = (0,
|
|
7000
|
+
const regDir = import_node_path41.default.dirname(regPath);
|
|
7001
|
+
const regBase = import_node_path41.default.basename(regPath);
|
|
7002
|
+
registryWatcher = (0, import_node_fs25.watch)(regDir, (_eventType, filename) => {
|
|
6238
7003
|
if (filename !== null && filename !== regBase) return;
|
|
6239
7004
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
6240
7005
|
reloadTimer = setTimeout(() => {
|
|
@@ -6277,7 +7042,7 @@ async function startDaemon(opts = {}) {
|
|
|
6277
7042
|
} catch {
|
|
6278
7043
|
}
|
|
6279
7044
|
}
|
|
6280
|
-
await
|
|
7045
|
+
await import_node_fs25.promises.unlink(pidPath).catch(() => {
|
|
6281
7046
|
});
|
|
6282
7047
|
};
|
|
6283
7048
|
return {
|
|
@@ -6297,9 +7062,9 @@ init_auth();
|
|
|
6297
7062
|
|
|
6298
7063
|
// src/web-spawn.ts
|
|
6299
7064
|
init_cjs_shims();
|
|
6300
|
-
var
|
|
7065
|
+
var import_node_child_process2 = require("child_process");
|
|
6301
7066
|
var import_node_net = __toESM(require("net"), 1);
|
|
6302
|
-
var
|
|
7067
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
6303
7068
|
var DEFAULT_WEB_PORT = 6328;
|
|
6304
7069
|
async function assertPortFree(port) {
|
|
6305
7070
|
await new Promise((resolve, reject) => {
|
|
@@ -6327,10 +7092,10 @@ function resolveWebPackageDir() {
|
|
|
6327
7092
|
eval("require")
|
|
6328
7093
|
);
|
|
6329
7094
|
const pkgJsonPath = req.resolve("@neat.is/web/package.json");
|
|
6330
|
-
return
|
|
7095
|
+
return import_node_path42.default.dirname(pkgJsonPath);
|
|
6331
7096
|
}
|
|
6332
7097
|
function resolveStandaloneServerEntry(webDir) {
|
|
6333
|
-
return
|
|
7098
|
+
return import_node_path42.default.join(webDir, ".next/standalone/packages/web/server.js");
|
|
6334
7099
|
}
|
|
6335
7100
|
async function spawnWebUI(restPort) {
|
|
6336
7101
|
const portRaw = process.env.NEAT_WEB_PORT;
|
|
@@ -6354,8 +7119,8 @@ async function spawnWebUI(restPort) {
|
|
|
6354
7119
|
HOSTNAME: process.env.HOSTNAME ?? "0.0.0.0",
|
|
6355
7120
|
NEAT_API_URL: process.env.NEAT_API_URL ?? `http://localhost:${restPort}`
|
|
6356
7121
|
};
|
|
6357
|
-
const child = (0,
|
|
6358
|
-
cwd:
|
|
7122
|
+
const child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
|
|
7123
|
+
cwd: import_node_path42.default.dirname(serverEntry),
|
|
6359
7124
|
env,
|
|
6360
7125
|
stdio: ["ignore", "inherit", "inherit"],
|
|
6361
7126
|
detached: false
|
|
@@ -6468,14 +7233,14 @@ function localVersion() {
|
|
|
6468
7233
|
}
|
|
6469
7234
|
function neatHome2() {
|
|
6470
7235
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
6471
|
-
return
|
|
7236
|
+
return import_node_path43.default.resolve(process.env.NEAT_HOME);
|
|
6472
7237
|
}
|
|
6473
7238
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
6474
|
-
return
|
|
7239
|
+
return import_node_path43.default.join(home, ".neat");
|
|
6475
7240
|
}
|
|
6476
7241
|
async function readPid() {
|
|
6477
7242
|
try {
|
|
6478
|
-
const raw = await
|
|
7243
|
+
const raw = await import_node_fs26.promises.readFile(import_node_path43.default.join(neatHome2(), "neatd.pid"), "utf8");
|
|
6479
7244
|
const n = Number.parseInt(raw.trim(), 10);
|
|
6480
7245
|
return Number.isFinite(n) ? n : null;
|
|
6481
7246
|
} catch {
|