@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/index.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", (req, reply, done) => {
|
|
60
|
-
const
|
|
60
|
+
const path42 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
61
61
|
for (const suffix of suffixes) {
|
|
62
|
-
if (
|
|
62
|
+
if (path42 === suffix || path42.endsWith(suffix)) {
|
|
63
63
|
done();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
@@ -185,8 +185,8 @@ function reshapeGrpcRequest(req) {
|
|
|
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 }
|
|
@@ -574,12 +574,12 @@ function logSpanHandler(span) {
|
|
|
574
574
|
`otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
|
|
575
575
|
);
|
|
576
576
|
}
|
|
577
|
-
var
|
|
577
|
+
var import_node_path39, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
578
578
|
var init_otel = __esm({
|
|
579
579
|
"src/otel.ts"() {
|
|
580
580
|
"use strict";
|
|
581
581
|
init_cjs_shims();
|
|
582
|
-
|
|
582
|
+
import_node_path39 = __toESM(require("path"), 1);
|
|
583
583
|
import_node_url2 = require("url");
|
|
584
584
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
585
585
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -1117,19 +1117,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1117
1117
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1118
1118
|
let best = { path: [start], edges: [] };
|
|
1119
1119
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1120
|
-
function step(node,
|
|
1121
|
-
if (
|
|
1122
|
-
best = { path: [...
|
|
1120
|
+
function step(node, path42, edges) {
|
|
1121
|
+
if (path42.length > best.path.length) {
|
|
1122
|
+
best = { path: [...path42], edges: [...edges] };
|
|
1123
1123
|
}
|
|
1124
|
-
if (
|
|
1124
|
+
if (path42.length - 1 >= maxDepth) return;
|
|
1125
1125
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1126
1126
|
for (const [srcId, edge] of incoming) {
|
|
1127
1127
|
if (visited.has(srcId)) continue;
|
|
1128
1128
|
visited.add(srcId);
|
|
1129
|
-
|
|
1129
|
+
path42.push(srcId);
|
|
1130
1130
|
edges.push(edge);
|
|
1131
|
-
step(srcId,
|
|
1132
|
-
|
|
1131
|
+
step(srcId, path42, edges);
|
|
1132
|
+
path42.pop();
|
|
1133
1133
|
edges.pop();
|
|
1134
1134
|
visited.delete(srcId);
|
|
1135
1135
|
}
|
|
@@ -1961,7 +1961,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1961
1961
|
graph.addNode(id, node);
|
|
1962
1962
|
return id;
|
|
1963
1963
|
}
|
|
1964
|
-
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
1964
|
+
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
1965
1965
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
1966
1966
|
const id = makeObservedEdgeId(type, source, target);
|
|
1967
1967
|
if (graph.hasEdge(id)) {
|
|
@@ -1998,7 +1998,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1998
1998
|
confidence: (0, import_types3.confidenceForObservedSignal)(signal),
|
|
1999
1999
|
lastObserved: ts,
|
|
2000
2000
|
callCount: 1,
|
|
2001
|
-
signal
|
|
2001
|
+
signal,
|
|
2002
|
+
// Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
|
|
2003
|
+
// Only set when code.filepath was present on the span — never fabricated.
|
|
2004
|
+
...evidence ? { evidence } : {}
|
|
2002
2005
|
};
|
|
2003
2006
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
2004
2007
|
return { edge, created: true };
|
|
@@ -2092,6 +2095,7 @@ async function handleSpan(ctx, span) {
|
|
|
2092
2095
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2093
2096
|
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
2094
2097
|
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
2098
|
+
const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
|
|
2095
2099
|
let affectedNode = sourceId;
|
|
2096
2100
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2097
2101
|
if (span.dbSystem) {
|
|
@@ -2105,7 +2109,8 @@ async function handleSpan(ctx, span) {
|
|
|
2105
2109
|
observedSource(),
|
|
2106
2110
|
targetId,
|
|
2107
2111
|
ts,
|
|
2108
|
-
isError
|
|
2112
|
+
isError,
|
|
2113
|
+
callSiteEvidence
|
|
2109
2114
|
);
|
|
2110
2115
|
if (result) affectedNode = targetId;
|
|
2111
2116
|
}
|
|
@@ -2121,7 +2126,8 @@ async function handleSpan(ctx, span) {
|
|
|
2121
2126
|
observedSource(),
|
|
2122
2127
|
targetId,
|
|
2123
2128
|
ts,
|
|
2124
|
-
isError
|
|
2129
|
+
isError,
|
|
2130
|
+
callSiteEvidence
|
|
2125
2131
|
);
|
|
2126
2132
|
affectedNode = targetId;
|
|
2127
2133
|
resolvedViaAddress = true;
|
|
@@ -2133,7 +2139,8 @@ async function handleSpan(ctx, span) {
|
|
|
2133
2139
|
observedSource(),
|
|
2134
2140
|
frontierNodeId,
|
|
2135
2141
|
ts,
|
|
2136
|
-
isError
|
|
2142
|
+
isError,
|
|
2143
|
+
callSiteEvidence
|
|
2137
2144
|
);
|
|
2138
2145
|
affectedNode = frontierNodeId;
|
|
2139
2146
|
resolvedViaAddress = true;
|
|
@@ -3082,16 +3089,433 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
3082
3089
|
await collectK8sAliases(graph, scanPath, byName);
|
|
3083
3090
|
}
|
|
3084
3091
|
|
|
3085
|
-
// src/extract/
|
|
3092
|
+
// src/extract/files.ts
|
|
3086
3093
|
init_cjs_shims();
|
|
3087
|
-
|
|
3094
|
+
|
|
3095
|
+
// src/extract/calls/shared.ts
|
|
3096
|
+
init_cjs_shims();
|
|
3097
|
+
var import_node_fs10 = require("fs");
|
|
3098
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
3088
3099
|
var import_types7 = require("@neat.is/types");
|
|
3100
|
+
async function walkSourceFiles(dir) {
|
|
3101
|
+
const out = [];
|
|
3102
|
+
async function walk(current) {
|
|
3103
|
+
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3104
|
+
for (const entry of entries) {
|
|
3105
|
+
const full = import_node_path10.default.join(current, entry.name);
|
|
3106
|
+
if (entry.isDirectory()) {
|
|
3107
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3108
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3109
|
+
await walk(full);
|
|
3110
|
+
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry.name))) {
|
|
3111
|
+
out.push(full);
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
await walk(dir);
|
|
3116
|
+
return out;
|
|
3117
|
+
}
|
|
3118
|
+
async function loadSourceFiles(dir) {
|
|
3119
|
+
const paths = await walkSourceFiles(dir);
|
|
3120
|
+
const out = [];
|
|
3121
|
+
for (const p of paths) {
|
|
3122
|
+
try {
|
|
3123
|
+
const content = await import_node_fs10.promises.readFile(p, "utf8");
|
|
3124
|
+
out.push({ path: p, content });
|
|
3125
|
+
} catch {
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
return out;
|
|
3129
|
+
}
|
|
3130
|
+
function lineOf(text, needle) {
|
|
3131
|
+
const idx = text.indexOf(needle);
|
|
3132
|
+
if (idx < 0) return 1;
|
|
3133
|
+
return text.slice(0, idx).split("\n").length;
|
|
3134
|
+
}
|
|
3135
|
+
function snippet(text, line) {
|
|
3136
|
+
const lines = text.split("\n");
|
|
3137
|
+
return (lines[line - 1] ?? "").trim();
|
|
3138
|
+
}
|
|
3139
|
+
function toPosix2(p) {
|
|
3140
|
+
return p.split("\\").join("/");
|
|
3141
|
+
}
|
|
3142
|
+
function languageForPath(relPath) {
|
|
3143
|
+
switch (import_node_path10.default.extname(relPath).toLowerCase()) {
|
|
3144
|
+
case ".py":
|
|
3145
|
+
return "python";
|
|
3146
|
+
case ".ts":
|
|
3147
|
+
case ".tsx":
|
|
3148
|
+
return "typescript";
|
|
3149
|
+
case ".js":
|
|
3150
|
+
case ".jsx":
|
|
3151
|
+
case ".mjs":
|
|
3152
|
+
case ".cjs":
|
|
3153
|
+
return "javascript";
|
|
3154
|
+
default:
|
|
3155
|
+
return void 0;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3159
|
+
let nodesAdded = 0;
|
|
3160
|
+
let edgesAdded = 0;
|
|
3161
|
+
const fileNodeId = (0, import_types7.fileId)(serviceName, relPath);
|
|
3162
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3163
|
+
const language = languageForPath(relPath);
|
|
3164
|
+
const node = {
|
|
3165
|
+
id: fileNodeId,
|
|
3166
|
+
type: import_types7.NodeType.FileNode,
|
|
3167
|
+
service: serviceName,
|
|
3168
|
+
path: relPath,
|
|
3169
|
+
...language ? { language } : {},
|
|
3170
|
+
discoveredVia: "static"
|
|
3171
|
+
};
|
|
3172
|
+
graph.addNode(fileNodeId, node);
|
|
3173
|
+
nodesAdded++;
|
|
3174
|
+
}
|
|
3175
|
+
const containsId = (0, import_types7.extractedEdgeId)(serviceNodeId, fileNodeId, import_types7.EdgeType.CONTAINS);
|
|
3176
|
+
if (!graph.hasEdge(containsId)) {
|
|
3177
|
+
const edge = {
|
|
3178
|
+
id: containsId,
|
|
3179
|
+
source: serviceNodeId,
|
|
3180
|
+
target: fileNodeId,
|
|
3181
|
+
type: import_types7.EdgeType.CONTAINS,
|
|
3182
|
+
provenance: import_types7.Provenance.EXTRACTED,
|
|
3183
|
+
confidence: (0, import_types7.confidenceForExtracted)("structural"),
|
|
3184
|
+
evidence: { file: relPath }
|
|
3185
|
+
};
|
|
3186
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3187
|
+
edgesAdded++;
|
|
3188
|
+
}
|
|
3189
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
// src/extract/files.ts
|
|
3193
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
3194
|
+
async function addFiles(graph, services) {
|
|
3195
|
+
let nodesAdded = 0;
|
|
3196
|
+
let edgesAdded = 0;
|
|
3197
|
+
for (const service of services) {
|
|
3198
|
+
const filePaths = await walkSourceFiles(service.dir);
|
|
3199
|
+
for (const filePath of filePaths) {
|
|
3200
|
+
const relPath = toPosix2(import_node_path11.default.relative(service.dir, filePath));
|
|
3201
|
+
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
3202
|
+
graph,
|
|
3203
|
+
service.pkg.name,
|
|
3204
|
+
service.node.id,
|
|
3205
|
+
relPath
|
|
3206
|
+
);
|
|
3207
|
+
nodesAdded += n;
|
|
3208
|
+
edgesAdded += e;
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
return { nodesAdded, edgesAdded };
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
// src/extract/imports.ts
|
|
3215
|
+
init_cjs_shims();
|
|
3216
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
3217
|
+
var import_node_fs11 = require("fs");
|
|
3218
|
+
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3219
|
+
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3220
|
+
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3221
|
+
var import_types8 = require("@neat.is/types");
|
|
3222
|
+
var PARSE_CHUNK = 16384;
|
|
3223
|
+
function parseSource(parser, source) {
|
|
3224
|
+
return parser.parse(
|
|
3225
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
|
|
3226
|
+
);
|
|
3227
|
+
}
|
|
3228
|
+
function makeJsParser() {
|
|
3229
|
+
const p = new import_tree_sitter.default();
|
|
3230
|
+
p.setLanguage(import_tree_sitter_javascript.default);
|
|
3231
|
+
return p;
|
|
3232
|
+
}
|
|
3233
|
+
function makePyParser() {
|
|
3234
|
+
const p = new import_tree_sitter.default();
|
|
3235
|
+
p.setLanguage(import_tree_sitter_python.default);
|
|
3236
|
+
return p;
|
|
3237
|
+
}
|
|
3238
|
+
function stringLiteralText(node) {
|
|
3239
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3240
|
+
const child = node.child(i);
|
|
3241
|
+
if (child?.type === "string_fragment") return child.text;
|
|
3242
|
+
}
|
|
3243
|
+
const raw = node.text;
|
|
3244
|
+
if (raw.length >= 2) return raw.slice(1, -1);
|
|
3245
|
+
return raw.length === 0 ? null : "";
|
|
3246
|
+
}
|
|
3247
|
+
function clipSnippet(text) {
|
|
3248
|
+
const oneLine = text.split("\n")[0] ?? text;
|
|
3249
|
+
return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
|
|
3250
|
+
}
|
|
3251
|
+
function collectJsImports(node, out) {
|
|
3252
|
+
if (node.type === "import_statement") {
|
|
3253
|
+
const source = node.childForFieldName("source");
|
|
3254
|
+
if (source) {
|
|
3255
|
+
const specifier = stringLiteralText(source);
|
|
3256
|
+
if (specifier) {
|
|
3257
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
if (node.type === "call_expression") {
|
|
3263
|
+
const fn = node.childForFieldName("function");
|
|
3264
|
+
if (fn?.type === "identifier" && fn.text === "require") {
|
|
3265
|
+
const args = node.childForFieldName("arguments");
|
|
3266
|
+
const firstArg = args?.namedChild(0);
|
|
3267
|
+
if (firstArg?.type === "string") {
|
|
3268
|
+
const specifier = stringLiteralText(firstArg);
|
|
3269
|
+
if (specifier) {
|
|
3270
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3276
|
+
const child = node.namedChild(i);
|
|
3277
|
+
if (child) collectJsImports(child, out);
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
function collectPyImports(node, out) {
|
|
3281
|
+
if (node.type === "import_from_statement") {
|
|
3282
|
+
let level = 0;
|
|
3283
|
+
let modulePath = "";
|
|
3284
|
+
let pastFrom = false;
|
|
3285
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3286
|
+
const child = node.child(i);
|
|
3287
|
+
if (!child) continue;
|
|
3288
|
+
if (!pastFrom) {
|
|
3289
|
+
if (child.type === "from") pastFrom = true;
|
|
3290
|
+
continue;
|
|
3291
|
+
}
|
|
3292
|
+
if (child.type === "import") break;
|
|
3293
|
+
if (child.type === "relative_import") {
|
|
3294
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
3295
|
+
const rc = child.child(j);
|
|
3296
|
+
if (!rc) continue;
|
|
3297
|
+
if (rc.type === "import_prefix") {
|
|
3298
|
+
for (let k = 0; k < rc.childCount; k++) {
|
|
3299
|
+
if (rc.child(k)?.type === ".") level++;
|
|
3300
|
+
}
|
|
3301
|
+
} else if (rc.type === "dotted_name") modulePath = rc.text;
|
|
3302
|
+
}
|
|
3303
|
+
break;
|
|
3304
|
+
}
|
|
3305
|
+
if (child.type === "dotted_name") {
|
|
3306
|
+
modulePath = child.text;
|
|
3307
|
+
break;
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
if (level > 0 || modulePath) {
|
|
3311
|
+
out.push({ modulePath, level, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3315
|
+
const child = node.namedChild(i);
|
|
3316
|
+
if (child) collectPyImports(child, out);
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
async function fileExists(p) {
|
|
3320
|
+
try {
|
|
3321
|
+
await import_node_fs11.promises.access(p);
|
|
3322
|
+
return true;
|
|
3323
|
+
} catch {
|
|
3324
|
+
return false;
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
function isWithinServiceDir(candidate, serviceDir) {
|
|
3328
|
+
const rel = import_node_path12.default.relative(serviceDir, candidate);
|
|
3329
|
+
return rel !== "" && !rel.startsWith("..") && !import_node_path12.default.isAbsolute(rel);
|
|
3330
|
+
}
|
|
3331
|
+
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
3332
|
+
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
3333
|
+
async function firstExistingCandidate(base, serviceDir) {
|
|
3334
|
+
for (const ext of JS_EXTENSIONS) {
|
|
3335
|
+
const candidate = base + ext;
|
|
3336
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
3337
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
for (const indexFile of JS_INDEX_FILES) {
|
|
3341
|
+
const candidate = import_node_path12.default.join(base, indexFile);
|
|
3342
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
3343
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
return null;
|
|
3347
|
+
}
|
|
3348
|
+
async function loadTsPathConfig(serviceDir) {
|
|
3349
|
+
const tsconfigPath = import_node_path12.default.join(serviceDir, "tsconfig.json");
|
|
3350
|
+
let raw;
|
|
3351
|
+
try {
|
|
3352
|
+
raw = await import_node_fs11.promises.readFile(tsconfigPath, "utf8");
|
|
3353
|
+
} catch {
|
|
3354
|
+
return null;
|
|
3355
|
+
}
|
|
3356
|
+
try {
|
|
3357
|
+
const parsed = JSON.parse(raw);
|
|
3358
|
+
const paths = parsed.compilerOptions?.paths;
|
|
3359
|
+
if (!paths || Object.keys(paths).length === 0) return null;
|
|
3360
|
+
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
3361
|
+
return { paths, baseDir: baseUrl ? import_node_path12.default.resolve(serviceDir, baseUrl) : serviceDir };
|
|
3362
|
+
} catch (err) {
|
|
3363
|
+
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
3364
|
+
return null;
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
async function resolveTsAlias(specifier, config, serviceDir) {
|
|
3368
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
3369
|
+
let suffix = null;
|
|
3370
|
+
if (pattern === specifier) {
|
|
3371
|
+
suffix = "";
|
|
3372
|
+
} else if (pattern.endsWith("/*")) {
|
|
3373
|
+
const prefix = pattern.slice(0, -1);
|
|
3374
|
+
if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
|
|
3375
|
+
}
|
|
3376
|
+
if (suffix === null) continue;
|
|
3377
|
+
for (const target of targets) {
|
|
3378
|
+
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
3379
|
+
const resolvedBase = import_node_path12.default.resolve(config.baseDir, targetBase, suffix);
|
|
3380
|
+
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
3381
|
+
if (hit) return hit;
|
|
3382
|
+
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
|
|
3383
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, resolvedBase));
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
return null;
|
|
3388
|
+
}
|
|
3389
|
+
async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
|
|
3390
|
+
if (!specifier) return null;
|
|
3391
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
3392
|
+
const base = import_node_path12.default.resolve(importerDir, specifier);
|
|
3393
|
+
const ext = import_node_path12.default.extname(specifier);
|
|
3394
|
+
if (ext) {
|
|
3395
|
+
if (ext === ".js" || ext === ".jsx") {
|
|
3396
|
+
const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
|
|
3397
|
+
const tsSibling = base.slice(0, -ext.length) + tsExt;
|
|
3398
|
+
if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
|
|
3399
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, tsSibling));
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
|
|
3403
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, base));
|
|
3404
|
+
}
|
|
3405
|
+
return null;
|
|
3406
|
+
}
|
|
3407
|
+
return firstExistingCandidate(base, serviceDir);
|
|
3408
|
+
}
|
|
3409
|
+
if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
|
|
3410
|
+
return null;
|
|
3411
|
+
}
|
|
3412
|
+
async function resolvePyImport(imp, importerPath, serviceDir) {
|
|
3413
|
+
if (!imp.modulePath) return null;
|
|
3414
|
+
const relPath = imp.modulePath.split(".").join("/");
|
|
3415
|
+
let baseDir;
|
|
3416
|
+
if (imp.level > 0) {
|
|
3417
|
+
baseDir = import_node_path12.default.dirname(importerPath);
|
|
3418
|
+
for (let i = 1; i < imp.level; i++) baseDir = import_node_path12.default.dirname(baseDir);
|
|
3419
|
+
} else {
|
|
3420
|
+
baseDir = serviceDir;
|
|
3421
|
+
}
|
|
3422
|
+
const candidates = [import_node_path12.default.join(baseDir, `${relPath}.py`), import_node_path12.default.join(baseDir, relPath, "__init__.py")];
|
|
3423
|
+
for (const candidate of candidates) {
|
|
3424
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
3425
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
return null;
|
|
3429
|
+
}
|
|
3430
|
+
function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
|
|
3431
|
+
const importeeFileId = (0, import_types8.fileId)(serviceName, importeeRelPath);
|
|
3432
|
+
if (!graph.hasNode(importeeFileId)) return 0;
|
|
3433
|
+
const edgeId = (0, import_types8.extractedEdgeId)(importerFileId, importeeFileId, import_types8.EdgeType.IMPORTS);
|
|
3434
|
+
if (graph.hasEdge(edgeId)) return 0;
|
|
3435
|
+
const edge = {
|
|
3436
|
+
id: edgeId,
|
|
3437
|
+
source: importerFileId,
|
|
3438
|
+
target: importeeFileId,
|
|
3439
|
+
type: import_types8.EdgeType.IMPORTS,
|
|
3440
|
+
provenance: import_types8.Provenance.EXTRACTED,
|
|
3441
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3442
|
+
evidence: { file: importerRelPath, line, snippet: snippet2 }
|
|
3443
|
+
};
|
|
3444
|
+
graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
|
|
3445
|
+
return 1;
|
|
3446
|
+
}
|
|
3447
|
+
async function addImports(graph, services) {
|
|
3448
|
+
const jsParser = makeJsParser();
|
|
3449
|
+
const pyParser = makePyParser();
|
|
3450
|
+
let edgesAdded = 0;
|
|
3451
|
+
for (const service of services) {
|
|
3452
|
+
const tsPaths = await loadTsPathConfig(service.dir);
|
|
3453
|
+
const files = await loadSourceFiles(service.dir);
|
|
3454
|
+
for (const file of files) {
|
|
3455
|
+
if (isTestPath(file.path)) continue;
|
|
3456
|
+
const relFile = toPosix2(import_node_path12.default.relative(service.dir, file.path));
|
|
3457
|
+
const importerFileId = (0, import_types8.fileId)(service.pkg.name, relFile);
|
|
3458
|
+
const isPython = import_node_path12.default.extname(file.path) === ".py";
|
|
3459
|
+
if (isPython) {
|
|
3460
|
+
let pyImports = [];
|
|
3461
|
+
try {
|
|
3462
|
+
const tree = parseSource(pyParser, file.content);
|
|
3463
|
+
collectPyImports(tree.rootNode, pyImports);
|
|
3464
|
+
} catch (err) {
|
|
3465
|
+
recordExtractionError("import extraction", file.path, err);
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3468
|
+
for (const imp of pyImports) {
|
|
3469
|
+
const resolved = await resolvePyImport(imp, file.path, service.dir);
|
|
3470
|
+
if (!resolved) continue;
|
|
3471
|
+
edgesAdded += emitImportEdge(
|
|
3472
|
+
graph,
|
|
3473
|
+
service.pkg.name,
|
|
3474
|
+
importerFileId,
|
|
3475
|
+
relFile,
|
|
3476
|
+
resolved,
|
|
3477
|
+
imp.line,
|
|
3478
|
+
imp.snippet
|
|
3479
|
+
);
|
|
3480
|
+
}
|
|
3481
|
+
continue;
|
|
3482
|
+
}
|
|
3483
|
+
let jsImports = [];
|
|
3484
|
+
try {
|
|
3485
|
+
const tree = parseSource(jsParser, file.content);
|
|
3486
|
+
collectJsImports(tree.rootNode, jsImports);
|
|
3487
|
+
} catch (err) {
|
|
3488
|
+
recordExtractionError("import extraction", file.path, err);
|
|
3489
|
+
continue;
|
|
3490
|
+
}
|
|
3491
|
+
for (const imp of jsImports) {
|
|
3492
|
+
const resolved = await resolveJsImport(imp.specifier, import_node_path12.default.dirname(file.path), service.dir, tsPaths);
|
|
3493
|
+
if (!resolved) continue;
|
|
3494
|
+
edgesAdded += emitImportEdge(
|
|
3495
|
+
graph,
|
|
3496
|
+
service.pkg.name,
|
|
3497
|
+
importerFileId,
|
|
3498
|
+
relFile,
|
|
3499
|
+
resolved,
|
|
3500
|
+
imp.line,
|
|
3501
|
+
imp.snippet
|
|
3502
|
+
);
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
return { nodesAdded: 0, edgesAdded };
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
// src/extract/databases/index.ts
|
|
3510
|
+
init_cjs_shims();
|
|
3511
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
3512
|
+
var import_types9 = require("@neat.is/types");
|
|
3089
3513
|
|
|
3090
3514
|
// src/extract/databases/db-config-yaml.ts
|
|
3091
3515
|
init_cjs_shims();
|
|
3092
|
-
var
|
|
3516
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
3093
3517
|
async function parse(serviceDir) {
|
|
3094
|
-
const yamlPath =
|
|
3518
|
+
const yamlPath = import_node_path13.default.join(serviceDir, "db-config.yaml");
|
|
3095
3519
|
if (!await exists(yamlPath)) return [];
|
|
3096
3520
|
const raw = await readYaml(yamlPath);
|
|
3097
3521
|
return [
|
|
@@ -3109,13 +3533,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
3109
3533
|
|
|
3110
3534
|
// src/extract/databases/dotenv.ts
|
|
3111
3535
|
init_cjs_shims();
|
|
3112
|
-
var
|
|
3113
|
-
var
|
|
3536
|
+
var import_node_fs13 = require("fs");
|
|
3537
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
3114
3538
|
|
|
3115
3539
|
// src/extract/databases/shared.ts
|
|
3116
3540
|
init_cjs_shims();
|
|
3117
|
-
var
|
|
3118
|
-
var
|
|
3541
|
+
var import_node_fs12 = require("fs");
|
|
3542
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
3119
3543
|
function schemeToEngine(scheme) {
|
|
3120
3544
|
const s = scheme.toLowerCase().split("+")[0];
|
|
3121
3545
|
switch (s) {
|
|
@@ -3154,14 +3578,14 @@ function parseConnectionString(url) {
|
|
|
3154
3578
|
}
|
|
3155
3579
|
async function readIfExists(filePath) {
|
|
3156
3580
|
try {
|
|
3157
|
-
return await
|
|
3581
|
+
return await import_node_fs12.promises.readFile(filePath, "utf8");
|
|
3158
3582
|
} catch {
|
|
3159
3583
|
return null;
|
|
3160
3584
|
}
|
|
3161
3585
|
}
|
|
3162
3586
|
async function findFirst(serviceDir, candidates) {
|
|
3163
3587
|
for (const rel of candidates) {
|
|
3164
|
-
const abs =
|
|
3588
|
+
const abs = import_node_path14.default.join(serviceDir, rel);
|
|
3165
3589
|
const content = await readIfExists(abs);
|
|
3166
3590
|
if (content !== null) return abs;
|
|
3167
3591
|
}
|
|
@@ -3212,15 +3636,15 @@ function parseDotenvLine(line) {
|
|
|
3212
3636
|
return { key, value };
|
|
3213
3637
|
}
|
|
3214
3638
|
async function parse2(serviceDir) {
|
|
3215
|
-
const entries = await
|
|
3639
|
+
const entries = await import_node_fs13.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
3216
3640
|
const configs = [];
|
|
3217
3641
|
const seen = /* @__PURE__ */ new Set();
|
|
3218
3642
|
for (const entry of entries) {
|
|
3219
3643
|
if (!entry.isFile()) continue;
|
|
3220
3644
|
const match = isConfigFile(entry.name);
|
|
3221
3645
|
if (!match.match || match.fileType !== "env") continue;
|
|
3222
|
-
const filePath =
|
|
3223
|
-
const content = await
|
|
3646
|
+
const filePath = import_node_path15.default.join(serviceDir, entry.name);
|
|
3647
|
+
const content = await import_node_fs13.promises.readFile(filePath, "utf8");
|
|
3224
3648
|
for (const line of content.split("\n")) {
|
|
3225
3649
|
const parsed = parseDotenvLine(line);
|
|
3226
3650
|
if (!parsed) continue;
|
|
@@ -3239,9 +3663,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
3239
3663
|
|
|
3240
3664
|
// src/extract/databases/prisma.ts
|
|
3241
3665
|
init_cjs_shims();
|
|
3242
|
-
var
|
|
3666
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
3243
3667
|
async function parse3(serviceDir) {
|
|
3244
|
-
const schemaPath =
|
|
3668
|
+
const schemaPath = import_node_path16.default.join(serviceDir, "prisma", "schema.prisma");
|
|
3245
3669
|
const content = await readIfExists(schemaPath);
|
|
3246
3670
|
if (!content) return [];
|
|
3247
3671
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -3373,10 +3797,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
3373
3797
|
|
|
3374
3798
|
// src/extract/databases/ormconfig.ts
|
|
3375
3799
|
init_cjs_shims();
|
|
3376
|
-
var
|
|
3800
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
3377
3801
|
async function parse6(serviceDir) {
|
|
3378
3802
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
3379
|
-
const abs =
|
|
3803
|
+
const abs = import_node_path17.default.join(serviceDir, candidate);
|
|
3380
3804
|
if (!await exists(abs)) continue;
|
|
3381
3805
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
3382
3806
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -3436,9 +3860,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
3436
3860
|
|
|
3437
3861
|
// src/extract/databases/sequelize.ts
|
|
3438
3862
|
init_cjs_shims();
|
|
3439
|
-
var
|
|
3863
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3440
3864
|
async function parse8(serviceDir) {
|
|
3441
|
-
const configPath =
|
|
3865
|
+
const configPath = import_node_path18.default.join(serviceDir, "config", "config.json");
|
|
3442
3866
|
if (!await exists(configPath)) return [];
|
|
3443
3867
|
const raw = await readJson(configPath);
|
|
3444
3868
|
const out = [];
|
|
@@ -3465,7 +3889,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
3465
3889
|
|
|
3466
3890
|
// src/extract/databases/docker-compose.ts
|
|
3467
3891
|
init_cjs_shims();
|
|
3468
|
-
var
|
|
3892
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3469
3893
|
function portFromService(svc) {
|
|
3470
3894
|
for (const raw of svc.ports ?? []) {
|
|
3471
3895
|
const str = String(raw);
|
|
@@ -3492,7 +3916,7 @@ function databaseFromEnv(svc) {
|
|
|
3492
3916
|
}
|
|
3493
3917
|
async function parse9(serviceDir) {
|
|
3494
3918
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3495
|
-
const abs =
|
|
3919
|
+
const abs = import_node_path19.default.join(serviceDir, name);
|
|
3496
3920
|
if (!await exists(abs)) continue;
|
|
3497
3921
|
const raw = await readYaml(abs);
|
|
3498
3922
|
if (!raw?.services) return [];
|
|
@@ -3533,8 +3957,8 @@ function compatibleDriversFor(engine) {
|
|
|
3533
3957
|
}
|
|
3534
3958
|
function toDatabaseNode(config) {
|
|
3535
3959
|
return {
|
|
3536
|
-
id: (0,
|
|
3537
|
-
type:
|
|
3960
|
+
id: (0, import_types9.databaseId)(config.host),
|
|
3961
|
+
type: import_types9.NodeType.DatabaseNode,
|
|
3538
3962
|
name: config.database || config.host,
|
|
3539
3963
|
engine: config.engine,
|
|
3540
3964
|
engineVersion: config.engineVersion,
|
|
@@ -3664,19 +4088,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3664
4088
|
discoveredVia: mergedDiscoveredVia
|
|
3665
4089
|
});
|
|
3666
4090
|
}
|
|
4091
|
+
const relConfigFile = toPosix2(import_node_path20.default.relative(service.dir, config.sourceFile));
|
|
4092
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4093
|
+
graph,
|
|
4094
|
+
service.pkg.name,
|
|
4095
|
+
service.node.id,
|
|
4096
|
+
relConfigFile
|
|
4097
|
+
);
|
|
4098
|
+
nodesAdded += fn;
|
|
4099
|
+
edgesAdded += fe;
|
|
4100
|
+
const evidenceFile = toPosix2(import_node_path20.default.relative(scanPath, config.sourceFile));
|
|
3667
4101
|
const edge = {
|
|
3668
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3669
|
-
source:
|
|
4102
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
|
|
4103
|
+
source: fileNodeId,
|
|
3670
4104
|
target: dbNode.id,
|
|
3671
|
-
type:
|
|
3672
|
-
provenance:
|
|
3673
|
-
confidence: (0,
|
|
3674
|
-
|
|
3675
|
-
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3676
|
-
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3677
|
-
evidence: {
|
|
3678
|
-
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3679
|
-
}
|
|
4105
|
+
type: import_types9.EdgeType.CONNECTS_TO,
|
|
4106
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
4107
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
4108
|
+
evidence: { file: evidenceFile }
|
|
3680
4109
|
};
|
|
3681
4110
|
if (!graph.hasEdge(edge.id)) {
|
|
3682
4111
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3702,15 +4131,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3702
4131
|
|
|
3703
4132
|
// src/extract/configs.ts
|
|
3704
4133
|
init_cjs_shims();
|
|
3705
|
-
var
|
|
3706
|
-
var
|
|
3707
|
-
var
|
|
4134
|
+
var import_node_fs14 = require("fs");
|
|
4135
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
4136
|
+
var import_types10 = require("@neat.is/types");
|
|
3708
4137
|
async function walkConfigFiles(dir) {
|
|
3709
4138
|
const out = [];
|
|
3710
4139
|
async function walk(current) {
|
|
3711
|
-
const entries = await
|
|
4140
|
+
const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
|
|
3712
4141
|
for (const entry of entries) {
|
|
3713
|
-
const full =
|
|
4142
|
+
const full = import_node_path21.default.join(current, entry.name);
|
|
3714
4143
|
if (entry.isDirectory()) {
|
|
3715
4144
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3716
4145
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -3728,147 +4157,57 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3728
4157
|
let edgesAdded = 0;
|
|
3729
4158
|
for (const service of services) {
|
|
3730
4159
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3731
|
-
for (const file of configFiles) {
|
|
3732
|
-
const relPath =
|
|
3733
|
-
const node = {
|
|
3734
|
-
id: (0,
|
|
3735
|
-
type:
|
|
3736
|
-
name:
|
|
3737
|
-
path: relPath,
|
|
3738
|
-
fileType: isConfigFile(
|
|
3739
|
-
};
|
|
3740
|
-
if (!graph.hasNode(node.id)) {
|
|
3741
|
-
graph.addNode(node.id, node);
|
|
3742
|
-
nodesAdded++;
|
|
3743
|
-
}
|
|
3744
|
-
const
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
}
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
init_cjs_shims();
|
|
3768
|
-
var import_node_path20 = __toESM(require("path"), 1);
|
|
3769
|
-
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3770
|
-
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3771
|
-
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3772
|
-
var import_types10 = require("@neat.is/types");
|
|
3773
|
-
|
|
3774
|
-
// src/extract/calls/shared.ts
|
|
3775
|
-
init_cjs_shims();
|
|
3776
|
-
var import_node_fs13 = require("fs");
|
|
3777
|
-
var import_node_path19 = __toESM(require("path"), 1);
|
|
3778
|
-
var import_types9 = require("@neat.is/types");
|
|
3779
|
-
async function walkSourceFiles(dir) {
|
|
3780
|
-
const out = [];
|
|
3781
|
-
async function walk(current) {
|
|
3782
|
-
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3783
|
-
for (const entry of entries) {
|
|
3784
|
-
const full = import_node_path19.default.join(current, entry.name);
|
|
3785
|
-
if (entry.isDirectory()) {
|
|
3786
|
-
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3787
|
-
if (await isPythonVenvDir(full)) continue;
|
|
3788
|
-
await walk(full);
|
|
3789
|
-
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry.name))) {
|
|
3790
|
-
out.push(full);
|
|
3791
|
-
}
|
|
3792
|
-
}
|
|
3793
|
-
}
|
|
3794
|
-
await walk(dir);
|
|
3795
|
-
return out;
|
|
3796
|
-
}
|
|
3797
|
-
async function loadSourceFiles(dir) {
|
|
3798
|
-
const paths = await walkSourceFiles(dir);
|
|
3799
|
-
const out = [];
|
|
3800
|
-
for (const p of paths) {
|
|
3801
|
-
try {
|
|
3802
|
-
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3803
|
-
out.push({ path: p, content });
|
|
3804
|
-
} catch {
|
|
3805
|
-
}
|
|
3806
|
-
}
|
|
3807
|
-
return out;
|
|
3808
|
-
}
|
|
3809
|
-
function lineOf(text, needle) {
|
|
3810
|
-
const idx = text.indexOf(needle);
|
|
3811
|
-
if (idx < 0) return 1;
|
|
3812
|
-
return text.slice(0, idx).split("\n").length;
|
|
3813
|
-
}
|
|
3814
|
-
function snippet(text, line) {
|
|
3815
|
-
const lines = text.split("\n");
|
|
3816
|
-
return (lines[line - 1] ?? "").trim();
|
|
3817
|
-
}
|
|
3818
|
-
function toPosix2(p) {
|
|
3819
|
-
return p.split("\\").join("/");
|
|
3820
|
-
}
|
|
3821
|
-
function languageForPath(relPath) {
|
|
3822
|
-
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3823
|
-
case ".py":
|
|
3824
|
-
return "python";
|
|
3825
|
-
case ".ts":
|
|
3826
|
-
case ".tsx":
|
|
3827
|
-
return "typescript";
|
|
3828
|
-
case ".js":
|
|
3829
|
-
case ".jsx":
|
|
3830
|
-
case ".mjs":
|
|
3831
|
-
case ".cjs":
|
|
3832
|
-
return "javascript";
|
|
3833
|
-
default:
|
|
3834
|
-
return void 0;
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3837
|
-
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3838
|
-
let nodesAdded = 0;
|
|
3839
|
-
let edgesAdded = 0;
|
|
3840
|
-
const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
|
|
3841
|
-
if (!graph.hasNode(fileNodeId)) {
|
|
3842
|
-
const language = languageForPath(relPath);
|
|
3843
|
-
const node = {
|
|
3844
|
-
id: fileNodeId,
|
|
3845
|
-
type: import_types9.NodeType.FileNode,
|
|
3846
|
-
service: serviceName,
|
|
3847
|
-
path: relPath,
|
|
3848
|
-
...language ? { language } : {},
|
|
3849
|
-
discoveredVia: "static"
|
|
3850
|
-
};
|
|
3851
|
-
graph.addNode(fileNodeId, node);
|
|
3852
|
-
nodesAdded++;
|
|
3853
|
-
}
|
|
3854
|
-
const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
|
|
3855
|
-
if (!graph.hasEdge(containsId)) {
|
|
3856
|
-
const edge = {
|
|
3857
|
-
id: containsId,
|
|
3858
|
-
source: serviceNodeId,
|
|
3859
|
-
target: fileNodeId,
|
|
3860
|
-
type: import_types9.EdgeType.CONTAINS,
|
|
3861
|
-
provenance: import_types9.Provenance.EXTRACTED,
|
|
3862
|
-
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3863
|
-
evidence: { file: relPath }
|
|
3864
|
-
};
|
|
3865
|
-
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3866
|
-
edgesAdded++;
|
|
4160
|
+
for (const file of configFiles) {
|
|
4161
|
+
const relPath = import_node_path21.default.relative(scanPath, file);
|
|
4162
|
+
const node = {
|
|
4163
|
+
id: (0, import_types10.configId)(relPath),
|
|
4164
|
+
type: import_types10.NodeType.ConfigNode,
|
|
4165
|
+
name: import_node_path21.default.basename(file),
|
|
4166
|
+
path: relPath,
|
|
4167
|
+
fileType: isConfigFile(import_node_path21.default.basename(file)).fileType
|
|
4168
|
+
};
|
|
4169
|
+
if (!graph.hasNode(node.id)) {
|
|
4170
|
+
graph.addNode(node.id, node);
|
|
4171
|
+
nodesAdded++;
|
|
4172
|
+
}
|
|
4173
|
+
const relToService = toPosix2(import_node_path21.default.relative(service.dir, file));
|
|
4174
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4175
|
+
graph,
|
|
4176
|
+
service.pkg.name,
|
|
4177
|
+
service.node.id,
|
|
4178
|
+
relToService
|
|
4179
|
+
);
|
|
4180
|
+
nodesAdded += fn;
|
|
4181
|
+
edgesAdded += fe;
|
|
4182
|
+
const edge = {
|
|
4183
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types10.EdgeType.CONFIGURED_BY),
|
|
4184
|
+
source: fileNodeId,
|
|
4185
|
+
target: node.id,
|
|
4186
|
+
type: import_types10.EdgeType.CONFIGURED_BY,
|
|
4187
|
+
provenance: import_types10.Provenance.EXTRACTED,
|
|
4188
|
+
confidence: (0, import_types10.confidenceForExtracted)("structural"),
|
|
4189
|
+
evidence: { file: relPath.split(import_node_path21.default.sep).join("/") }
|
|
4190
|
+
};
|
|
4191
|
+
if (!graph.hasEdge(edge.id)) {
|
|
4192
|
+
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
4193
|
+
edgesAdded++;
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
3867
4196
|
}
|
|
3868
|
-
return {
|
|
4197
|
+
return { nodesAdded, edgesAdded };
|
|
3869
4198
|
}
|
|
3870
4199
|
|
|
4200
|
+
// src/extract/calls/index.ts
|
|
4201
|
+
init_cjs_shims();
|
|
4202
|
+
var import_types16 = require("@neat.is/types");
|
|
4203
|
+
|
|
3871
4204
|
// src/extract/calls/http.ts
|
|
4205
|
+
init_cjs_shims();
|
|
4206
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
4207
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
4208
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
4209
|
+
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
4210
|
+
var import_types11 = require("@neat.is/types");
|
|
3872
4211
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
3873
4212
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
3874
4213
|
function isInsideJsxExternalLink(node) {
|
|
@@ -3896,14 +4235,14 @@ function collectStringLiterals(node, out) {
|
|
|
3896
4235
|
if (child) collectStringLiterals(child, out);
|
|
3897
4236
|
}
|
|
3898
4237
|
}
|
|
3899
|
-
var
|
|
3900
|
-
function
|
|
4238
|
+
var PARSE_CHUNK2 = 16384;
|
|
4239
|
+
function parseSource2(parser, source) {
|
|
3901
4240
|
return parser.parse(
|
|
3902
|
-
(index) => index >= source.length ? "" : source.slice(index, index +
|
|
4241
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
3903
4242
|
);
|
|
3904
4243
|
}
|
|
3905
4244
|
function callsFromSource(source, parser, knownHosts) {
|
|
3906
|
-
const tree =
|
|
4245
|
+
const tree = parseSource2(parser, source);
|
|
3907
4246
|
const literals = [];
|
|
3908
4247
|
collectStringLiterals(tree.rootNode, literals);
|
|
3909
4248
|
const out = [];
|
|
@@ -3917,25 +4256,25 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3917
4256
|
}
|
|
3918
4257
|
return out;
|
|
3919
4258
|
}
|
|
3920
|
-
function
|
|
3921
|
-
const p = new
|
|
3922
|
-
p.setLanguage(
|
|
4259
|
+
function makeJsParser2() {
|
|
4260
|
+
const p = new import_tree_sitter2.default();
|
|
4261
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
3923
4262
|
return p;
|
|
3924
4263
|
}
|
|
3925
|
-
function
|
|
3926
|
-
const p = new
|
|
3927
|
-
p.setLanguage(
|
|
4264
|
+
function makePyParser2() {
|
|
4265
|
+
const p = new import_tree_sitter2.default();
|
|
4266
|
+
p.setLanguage(import_tree_sitter_python2.default);
|
|
3928
4267
|
return p;
|
|
3929
4268
|
}
|
|
3930
4269
|
async function addHttpCallEdges(graph, services) {
|
|
3931
|
-
const jsParser =
|
|
3932
|
-
const pyParser =
|
|
4270
|
+
const jsParser = makeJsParser2();
|
|
4271
|
+
const pyParser = makePyParser2();
|
|
3933
4272
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
3934
4273
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3935
4274
|
for (const service of services) {
|
|
3936
|
-
knownHosts.add(
|
|
4275
|
+
knownHosts.add(import_node_path22.default.basename(service.dir));
|
|
3937
4276
|
knownHosts.add(service.pkg.name);
|
|
3938
|
-
hostToNodeId.set(
|
|
4277
|
+
hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
|
|
3939
4278
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3940
4279
|
}
|
|
3941
4280
|
let nodesAdded = 0;
|
|
@@ -3945,7 +4284,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3945
4284
|
const seen = /* @__PURE__ */ new Set();
|
|
3946
4285
|
for (const file of files) {
|
|
3947
4286
|
if (isTestPath(file.path)) continue;
|
|
3948
|
-
const parser =
|
|
4287
|
+
const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3949
4288
|
let sites;
|
|
3950
4289
|
try {
|
|
3951
4290
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -3954,14 +4293,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3954
4293
|
continue;
|
|
3955
4294
|
}
|
|
3956
4295
|
if (sites.length === 0) continue;
|
|
3957
|
-
const relFile = toPosix2(
|
|
4296
|
+
const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
|
|
3958
4297
|
for (const site of sites) {
|
|
3959
4298
|
const targetId = hostToNodeId.get(site.host);
|
|
3960
4299
|
if (!targetId || targetId === service.node.id) continue;
|
|
3961
4300
|
const dedupKey = `${relFile}|${targetId}`;
|
|
3962
4301
|
if (seen.has(dedupKey)) continue;
|
|
3963
4302
|
seen.add(dedupKey);
|
|
3964
|
-
const confidence = (0,
|
|
4303
|
+
const confidence = (0, import_types11.confidenceForExtracted)("hostname-shape-match");
|
|
3965
4304
|
const ev = {
|
|
3966
4305
|
file: relFile,
|
|
3967
4306
|
line: site.line,
|
|
@@ -3975,25 +4314,25 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3975
4314
|
);
|
|
3976
4315
|
nodesAdded += n;
|
|
3977
4316
|
edgesAdded += e;
|
|
3978
|
-
if (!(0,
|
|
4317
|
+
if (!(0, import_types11.passesExtractedFloor)(confidence)) {
|
|
3979
4318
|
noteExtractedDropped({
|
|
3980
4319
|
source: fileNodeId,
|
|
3981
4320
|
target: targetId,
|
|
3982
|
-
type:
|
|
4321
|
+
type: import_types11.EdgeType.CALLS,
|
|
3983
4322
|
confidence,
|
|
3984
4323
|
confidenceKind: "hostname-shape-match",
|
|
3985
4324
|
evidence: ev
|
|
3986
4325
|
});
|
|
3987
4326
|
continue;
|
|
3988
4327
|
}
|
|
3989
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId,
|
|
4328
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types11.EdgeType.CALLS);
|
|
3990
4329
|
if (!graph.hasEdge(edgeId)) {
|
|
3991
4330
|
const edge = {
|
|
3992
4331
|
id: edgeId,
|
|
3993
4332
|
source: fileNodeId,
|
|
3994
4333
|
target: targetId,
|
|
3995
|
-
type:
|
|
3996
|
-
provenance:
|
|
4334
|
+
type: import_types11.EdgeType.CALLS,
|
|
4335
|
+
provenance: import_types11.Provenance.EXTRACTED,
|
|
3997
4336
|
confidence,
|
|
3998
4337
|
evidence: ev
|
|
3999
4338
|
};
|
|
@@ -4008,8 +4347,8 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4008
4347
|
|
|
4009
4348
|
// src/extract/calls/kafka.ts
|
|
4010
4349
|
init_cjs_shims();
|
|
4011
|
-
var
|
|
4012
|
-
var
|
|
4350
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
4351
|
+
var import_types12 = require("@neat.is/types");
|
|
4013
4352
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4014
4353
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
4015
4354
|
function findAll(re, text) {
|
|
@@ -4030,7 +4369,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4030
4369
|
seen.add(key);
|
|
4031
4370
|
const line = lineOf(file.content, topic);
|
|
4032
4371
|
out.push({
|
|
4033
|
-
infraId: (0,
|
|
4372
|
+
infraId: (0, import_types12.infraId)("kafka-topic", topic),
|
|
4034
4373
|
name: topic,
|
|
4035
4374
|
kind: "kafka-topic",
|
|
4036
4375
|
edgeType,
|
|
@@ -4039,7 +4378,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4039
4378
|
// tier (ADR-066).
|
|
4040
4379
|
confidenceKind: "verified-call-site",
|
|
4041
4380
|
evidence: {
|
|
4042
|
-
file:
|
|
4381
|
+
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
4043
4382
|
line,
|
|
4044
4383
|
snippet: snippet(file.content, line)
|
|
4045
4384
|
}
|
|
@@ -4052,8 +4391,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4052
4391
|
|
|
4053
4392
|
// src/extract/calls/redis.ts
|
|
4054
4393
|
init_cjs_shims();
|
|
4055
|
-
var
|
|
4056
|
-
var
|
|
4394
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
4395
|
+
var import_types13 = require("@neat.is/types");
|
|
4057
4396
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
4058
4397
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
4059
4398
|
const out = [];
|
|
@@ -4066,7 +4405,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4066
4405
|
seen.add(host);
|
|
4067
4406
|
const line = lineOf(file.content, host);
|
|
4068
4407
|
out.push({
|
|
4069
|
-
infraId: (0,
|
|
4408
|
+
infraId: (0, import_types13.infraId)("redis", host),
|
|
4070
4409
|
name: host,
|
|
4071
4410
|
kind: "redis",
|
|
4072
4411
|
edgeType: "CALLS",
|
|
@@ -4075,7 +4414,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4075
4414
|
// support tier (ADR-066).
|
|
4076
4415
|
confidenceKind: "url-with-structural-support",
|
|
4077
4416
|
evidence: {
|
|
4078
|
-
file:
|
|
4417
|
+
file: import_node_path24.default.relative(serviceDir, file.path),
|
|
4079
4418
|
line,
|
|
4080
4419
|
snippet: snippet(file.content, line)
|
|
4081
4420
|
}
|
|
@@ -4086,8 +4425,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4086
4425
|
|
|
4087
4426
|
// src/extract/calls/aws.ts
|
|
4088
4427
|
init_cjs_shims();
|
|
4089
|
-
var
|
|
4090
|
-
var
|
|
4428
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
4429
|
+
var import_types14 = require("@neat.is/types");
|
|
4091
4430
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4092
4431
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4093
4432
|
function hasMarker(text, markers) {
|
|
@@ -4111,7 +4450,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4111
4450
|
seen.add(key);
|
|
4112
4451
|
const line = lineOf(file.content, name);
|
|
4113
4452
|
out.push({
|
|
4114
|
-
infraId: (0,
|
|
4453
|
+
infraId: (0, import_types14.infraId)(kind, name),
|
|
4115
4454
|
name,
|
|
4116
4455
|
kind,
|
|
4117
4456
|
edgeType: "CALLS",
|
|
@@ -4120,7 +4459,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4120
4459
|
// (ADR-066).
|
|
4121
4460
|
confidenceKind: "verified-call-site",
|
|
4122
4461
|
evidence: {
|
|
4123
|
-
file:
|
|
4462
|
+
file: import_node_path25.default.relative(serviceDir, file.path),
|
|
4124
4463
|
line,
|
|
4125
4464
|
snippet: snippet(file.content, line)
|
|
4126
4465
|
}
|
|
@@ -4145,13 +4484,14 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4145
4484
|
|
|
4146
4485
|
// src/extract/calls/grpc.ts
|
|
4147
4486
|
init_cjs_shims();
|
|
4148
|
-
var
|
|
4149
|
-
var
|
|
4487
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
4488
|
+
var import_types15 = require("@neat.is/types");
|
|
4150
4489
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
4151
4490
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
4152
4491
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
4153
4492
|
function isLikelyAddress(value) {
|
|
4154
4493
|
if (!value) return false;
|
|
4494
|
+
if (/\s/.test(value) || value.startsWith("{")) return false;
|
|
4155
4495
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
4156
4496
|
}
|
|
4157
4497
|
function normaliseForMatch(s) {
|
|
@@ -4194,7 +4534,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4194
4534
|
const { kind } = classified;
|
|
4195
4535
|
const line = lineOf(file.content, m[0]);
|
|
4196
4536
|
out.push({
|
|
4197
|
-
infraId: (0,
|
|
4537
|
+
infraId: (0, import_types15.infraId)(kind, name),
|
|
4198
4538
|
name,
|
|
4199
4539
|
kind,
|
|
4200
4540
|
edgeType: "CALLS",
|
|
@@ -4203,7 +4543,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4203
4543
|
// tier (ADR-066).
|
|
4204
4544
|
confidenceKind: "verified-call-site",
|
|
4205
4545
|
evidence: {
|
|
4206
|
-
file:
|
|
4546
|
+
file: import_node_path26.default.relative(serviceDir, file.path),
|
|
4207
4547
|
line,
|
|
4208
4548
|
snippet: snippet(file.content, line)
|
|
4209
4549
|
}
|
|
@@ -4216,11 +4556,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4216
4556
|
function edgeTypeFromEndpoint(ep) {
|
|
4217
4557
|
switch (ep.edgeType) {
|
|
4218
4558
|
case "PUBLISHES_TO":
|
|
4219
|
-
return
|
|
4559
|
+
return import_types16.EdgeType.PUBLISHES_TO;
|
|
4220
4560
|
case "CONSUMES_FROM":
|
|
4221
|
-
return
|
|
4561
|
+
return import_types16.EdgeType.CONSUMES_FROM;
|
|
4222
4562
|
default:
|
|
4223
|
-
return
|
|
4563
|
+
return import_types16.EdgeType.CALLS;
|
|
4224
4564
|
}
|
|
4225
4565
|
}
|
|
4226
4566
|
function isAwsKind(kind) {
|
|
@@ -4247,7 +4587,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4247
4587
|
if (!graph.hasNode(ep.infraId)) {
|
|
4248
4588
|
const node = {
|
|
4249
4589
|
id: ep.infraId,
|
|
4250
|
-
type:
|
|
4590
|
+
type: import_types16.NodeType.InfraNode,
|
|
4251
4591
|
name: ep.name,
|
|
4252
4592
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
4253
4593
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -4259,7 +4599,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4259
4599
|
nodesAdded++;
|
|
4260
4600
|
}
|
|
4261
4601
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
4262
|
-
const confidence = (0,
|
|
4602
|
+
const confidence = (0, import_types16.confidenceForExtracted)(ep.confidenceKind);
|
|
4263
4603
|
const relFile = toPosix2(ep.evidence.file);
|
|
4264
4604
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
4265
4605
|
graph,
|
|
@@ -4269,7 +4609,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4269
4609
|
);
|
|
4270
4610
|
nodesAdded += n;
|
|
4271
4611
|
edgesAdded += e;
|
|
4272
|
-
if (!(0,
|
|
4612
|
+
if (!(0, import_types16.passesExtractedFloor)(confidence)) {
|
|
4273
4613
|
noteExtractedDropped({
|
|
4274
4614
|
source: fileNodeId,
|
|
4275
4615
|
target: ep.infraId,
|
|
@@ -4289,7 +4629,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4289
4629
|
source: fileNodeId,
|
|
4290
4630
|
target: ep.infraId,
|
|
4291
4631
|
type: edgeType,
|
|
4292
|
-
provenance:
|
|
4632
|
+
provenance: import_types16.Provenance.EXTRACTED,
|
|
4293
4633
|
confidence,
|
|
4294
4634
|
evidence: ep.evidence
|
|
4295
4635
|
};
|
|
@@ -4314,16 +4654,16 @@ init_cjs_shims();
|
|
|
4314
4654
|
|
|
4315
4655
|
// src/extract/infra/docker-compose.ts
|
|
4316
4656
|
init_cjs_shims();
|
|
4317
|
-
var
|
|
4318
|
-
var
|
|
4657
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
4658
|
+
var import_types18 = require("@neat.is/types");
|
|
4319
4659
|
|
|
4320
4660
|
// src/extract/infra/shared.ts
|
|
4321
4661
|
init_cjs_shims();
|
|
4322
|
-
var
|
|
4662
|
+
var import_types17 = require("@neat.is/types");
|
|
4323
4663
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
4324
4664
|
return {
|
|
4325
|
-
id: (0,
|
|
4326
|
-
type:
|
|
4665
|
+
id: (0, import_types17.infraId)(kind, name),
|
|
4666
|
+
type: import_types17.NodeType.InfraNode,
|
|
4327
4667
|
name,
|
|
4328
4668
|
provider,
|
|
4329
4669
|
kind,
|
|
@@ -4352,7 +4692,7 @@ function dependsOnList(value) {
|
|
|
4352
4692
|
}
|
|
4353
4693
|
function serviceNameToServiceNode(name, services) {
|
|
4354
4694
|
for (const s of services) {
|
|
4355
|
-
if (s.node.name === name ||
|
|
4695
|
+
if (s.node.name === name || import_node_path27.default.basename(s.dir) === name) return s.node.id;
|
|
4356
4696
|
}
|
|
4357
4697
|
return null;
|
|
4358
4698
|
}
|
|
@@ -4361,7 +4701,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4361
4701
|
let edgesAdded = 0;
|
|
4362
4702
|
let composePath = null;
|
|
4363
4703
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
4364
|
-
const abs =
|
|
4704
|
+
const abs = import_node_path27.default.join(scanPath, name);
|
|
4365
4705
|
if (await exists(abs)) {
|
|
4366
4706
|
composePath = abs;
|
|
4367
4707
|
break;
|
|
@@ -4374,13 +4714,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4374
4714
|
} catch (err) {
|
|
4375
4715
|
recordExtractionError(
|
|
4376
4716
|
"infra docker-compose",
|
|
4377
|
-
|
|
4717
|
+
import_node_path27.default.relative(scanPath, composePath),
|
|
4378
4718
|
err
|
|
4379
4719
|
);
|
|
4380
4720
|
return { nodesAdded, edgesAdded };
|
|
4381
4721
|
}
|
|
4382
4722
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
4383
|
-
const evidenceFile =
|
|
4723
|
+
const evidenceFile = import_node_path27.default.relative(scanPath, composePath).split(import_node_path27.default.sep).join("/");
|
|
4384
4724
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
4385
4725
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
4386
4726
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -4402,15 +4742,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4402
4742
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
4403
4743
|
const targetId = composeNameToNodeId.get(dep);
|
|
4404
4744
|
if (!targetId) continue;
|
|
4405
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
4745
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types18.EdgeType.DEPENDS_ON);
|
|
4406
4746
|
if (graph.hasEdge(edgeId)) continue;
|
|
4407
4747
|
const edge = {
|
|
4408
4748
|
id: edgeId,
|
|
4409
4749
|
source: sourceId,
|
|
4410
4750
|
target: targetId,
|
|
4411
|
-
type:
|
|
4412
|
-
provenance:
|
|
4413
|
-
confidence: (0,
|
|
4751
|
+
type: import_types18.EdgeType.DEPENDS_ON,
|
|
4752
|
+
provenance: import_types18.Provenance.EXTRACTED,
|
|
4753
|
+
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4414
4754
|
evidence: { file: evidenceFile }
|
|
4415
4755
|
};
|
|
4416
4756
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4422,9 +4762,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4422
4762
|
|
|
4423
4763
|
// src/extract/infra/dockerfile.ts
|
|
4424
4764
|
init_cjs_shims();
|
|
4425
|
-
var
|
|
4426
|
-
var
|
|
4427
|
-
var
|
|
4765
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
4766
|
+
var import_node_fs15 = require("fs");
|
|
4767
|
+
var import_types19 = require("@neat.is/types");
|
|
4428
4768
|
function runtimeImage(content) {
|
|
4429
4769
|
const lines = content.split("\n");
|
|
4430
4770
|
let last = null;
|
|
@@ -4443,15 +4783,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4443
4783
|
let nodesAdded = 0;
|
|
4444
4784
|
let edgesAdded = 0;
|
|
4445
4785
|
for (const service of services) {
|
|
4446
|
-
const dockerfilePath =
|
|
4786
|
+
const dockerfilePath = import_node_path28.default.join(service.dir, "Dockerfile");
|
|
4447
4787
|
if (!await exists(dockerfilePath)) continue;
|
|
4448
4788
|
let content;
|
|
4449
4789
|
try {
|
|
4450
|
-
content = await
|
|
4790
|
+
content = await import_node_fs15.promises.readFile(dockerfilePath, "utf8");
|
|
4451
4791
|
} catch (err) {
|
|
4452
4792
|
recordExtractionError(
|
|
4453
4793
|
"infra dockerfile",
|
|
4454
|
-
|
|
4794
|
+
import_node_path28.default.relative(scanPath, dockerfilePath),
|
|
4455
4795
|
err
|
|
4456
4796
|
);
|
|
4457
4797
|
continue;
|
|
@@ -4463,17 +4803,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4463
4803
|
graph.addNode(node.id, node);
|
|
4464
4804
|
nodesAdded++;
|
|
4465
4805
|
}
|
|
4466
|
-
const
|
|
4806
|
+
const relDockerfile = toPosix2(import_node_path28.default.relative(service.dir, dockerfilePath));
|
|
4807
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4808
|
+
graph,
|
|
4809
|
+
service.pkg.name,
|
|
4810
|
+
service.node.id,
|
|
4811
|
+
relDockerfile
|
|
4812
|
+
);
|
|
4813
|
+
nodesAdded += fn;
|
|
4814
|
+
edgesAdded += fe;
|
|
4815
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types19.EdgeType.RUNS_ON);
|
|
4467
4816
|
if (!graph.hasEdge(edgeId)) {
|
|
4468
4817
|
const edge = {
|
|
4469
4818
|
id: edgeId,
|
|
4470
|
-
source:
|
|
4819
|
+
source: fileNodeId,
|
|
4471
4820
|
target: node.id,
|
|
4472
|
-
type:
|
|
4473
|
-
provenance:
|
|
4474
|
-
confidence: (0,
|
|
4821
|
+
type: import_types19.EdgeType.RUNS_ON,
|
|
4822
|
+
provenance: import_types19.Provenance.EXTRACTED,
|
|
4823
|
+
confidence: (0, import_types19.confidenceForExtracted)("structural"),
|
|
4475
4824
|
evidence: {
|
|
4476
|
-
file:
|
|
4825
|
+
file: toPosix2(import_node_path28.default.relative(scanPath, dockerfilePath))
|
|
4477
4826
|
}
|
|
4478
4827
|
};
|
|
4479
4828
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4485,21 +4834,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4485
4834
|
|
|
4486
4835
|
// src/extract/infra/terraform.ts
|
|
4487
4836
|
init_cjs_shims();
|
|
4488
|
-
var
|
|
4489
|
-
var
|
|
4837
|
+
var import_node_fs16 = require("fs");
|
|
4838
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
4490
4839
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
4491
4840
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
4492
4841
|
if (depth > max) return [];
|
|
4493
4842
|
const out = [];
|
|
4494
|
-
const entries = await
|
|
4843
|
+
const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4495
4844
|
for (const entry of entries) {
|
|
4496
4845
|
if (entry.isDirectory()) {
|
|
4497
4846
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
4498
|
-
const child =
|
|
4847
|
+
const child = import_node_path29.default.join(start, entry.name);
|
|
4499
4848
|
if (await isPythonVenvDir(child)) continue;
|
|
4500
4849
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
4501
4850
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
4502
|
-
out.push(
|
|
4851
|
+
out.push(import_node_path29.default.join(start, entry.name));
|
|
4503
4852
|
}
|
|
4504
4853
|
}
|
|
4505
4854
|
return out;
|
|
@@ -4508,7 +4857,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4508
4857
|
let nodesAdded = 0;
|
|
4509
4858
|
const files = await walkTfFiles(scanPath);
|
|
4510
4859
|
for (const file of files) {
|
|
4511
|
-
const content = await
|
|
4860
|
+
const content = await import_node_fs16.promises.readFile(file, "utf8");
|
|
4512
4861
|
RESOURCE_RE.lastIndex = 0;
|
|
4513
4862
|
let m;
|
|
4514
4863
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -4526,8 +4875,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4526
4875
|
|
|
4527
4876
|
// src/extract/infra/k8s.ts
|
|
4528
4877
|
init_cjs_shims();
|
|
4529
|
-
var
|
|
4530
|
-
var
|
|
4878
|
+
var import_node_fs17 = require("fs");
|
|
4879
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
4531
4880
|
var import_yaml3 = require("yaml");
|
|
4532
4881
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
4533
4882
|
Service: "k8s-service",
|
|
@@ -4541,15 +4890,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
4541
4890
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
4542
4891
|
if (depth > max) return [];
|
|
4543
4892
|
const out = [];
|
|
4544
|
-
const entries = await
|
|
4893
|
+
const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4545
4894
|
for (const entry of entries) {
|
|
4546
4895
|
if (entry.isDirectory()) {
|
|
4547
4896
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4548
|
-
const child =
|
|
4897
|
+
const child = import_node_path30.default.join(start, entry.name);
|
|
4549
4898
|
if (await isPythonVenvDir(child)) continue;
|
|
4550
4899
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
4551
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
4552
|
-
out.push(
|
|
4900
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path30.default.extname(entry.name))) {
|
|
4901
|
+
out.push(import_node_path30.default.join(start, entry.name));
|
|
4553
4902
|
}
|
|
4554
4903
|
}
|
|
4555
4904
|
return out;
|
|
@@ -4558,7 +4907,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
4558
4907
|
let nodesAdded = 0;
|
|
4559
4908
|
const files = await walkYamlFiles2(scanPath);
|
|
4560
4909
|
for (const file of files) {
|
|
4561
|
-
const content = await
|
|
4910
|
+
const content = await import_node_fs17.promises.readFile(file, "utf8");
|
|
4562
4911
|
let docs;
|
|
4563
4912
|
try {
|
|
4564
4913
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -4593,17 +4942,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
4593
4942
|
}
|
|
4594
4943
|
|
|
4595
4944
|
// src/extract/index.ts
|
|
4596
|
-
var
|
|
4945
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4597
4946
|
|
|
4598
4947
|
// src/extract/retire.ts
|
|
4599
4948
|
init_cjs_shims();
|
|
4600
|
-
var
|
|
4601
|
-
var
|
|
4602
|
-
var
|
|
4949
|
+
var import_node_fs18 = require("fs");
|
|
4950
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
4951
|
+
var import_types20 = require("@neat.is/types");
|
|
4603
4952
|
function dropOrphanedFileNodes(graph) {
|
|
4604
4953
|
const orphans = [];
|
|
4605
4954
|
graph.forEachNode((id, attrs) => {
|
|
4606
|
-
if (attrs.type !==
|
|
4955
|
+
if (attrs.type !== import_types20.NodeType.FileNode) return;
|
|
4607
4956
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
4608
4957
|
orphans.push(id);
|
|
4609
4958
|
}
|
|
@@ -4616,14 +4965,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4616
4965
|
const bases = [scanPath, ...serviceDirs];
|
|
4617
4966
|
graph.forEachEdge((id, attrs) => {
|
|
4618
4967
|
const edge = attrs;
|
|
4619
|
-
if (edge.provenance !==
|
|
4968
|
+
if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
|
|
4620
4969
|
const evidenceFile = edge.evidence?.file;
|
|
4621
4970
|
if (!evidenceFile) return;
|
|
4622
|
-
if (
|
|
4623
|
-
if (!(0,
|
|
4971
|
+
if (import_node_path31.default.isAbsolute(evidenceFile)) {
|
|
4972
|
+
if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
|
|
4624
4973
|
return;
|
|
4625
4974
|
}
|
|
4626
|
-
const found = bases.some((base) => (0,
|
|
4975
|
+
const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path31.default.join(base, evidenceFile)));
|
|
4627
4976
|
if (!found) toDrop.push(id);
|
|
4628
4977
|
});
|
|
4629
4978
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -4638,6 +4987,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4638
4987
|
const services = await discoverServices(scanPath);
|
|
4639
4988
|
const phase1Nodes = addServiceNodes(graph, services);
|
|
4640
4989
|
await addServiceAliases(graph, scanPath, services);
|
|
4990
|
+
const fileEnum = await addFiles(graph, services);
|
|
4991
|
+
const importGraph = await addImports(graph, services);
|
|
4641
4992
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
4642
4993
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
4643
4994
|
const phase4 = await addCallEdges(graph, services);
|
|
@@ -4661,7 +5012,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4661
5012
|
}
|
|
4662
5013
|
const droppedEntries = drainDroppedExtracted();
|
|
4663
5014
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
4664
|
-
const rejectedPath =
|
|
5015
|
+
const rejectedPath = import_node_path32.default.join(import_node_path32.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
4665
5016
|
try {
|
|
4666
5017
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
4667
5018
|
} catch (err) {
|
|
@@ -4671,8 +5022,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4671
5022
|
}
|
|
4672
5023
|
}
|
|
4673
5024
|
const result = {
|
|
4674
|
-
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
4675
|
-
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
5025
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
5026
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
4676
5027
|
frontiersPromoted,
|
|
4677
5028
|
extractionErrors: errorEntries.length,
|
|
4678
5029
|
errorEntries,
|
|
@@ -4695,9 +5046,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4695
5046
|
|
|
4696
5047
|
// src/persist.ts
|
|
4697
5048
|
init_cjs_shims();
|
|
4698
|
-
var
|
|
4699
|
-
var
|
|
4700
|
-
var
|
|
5049
|
+
var import_node_fs19 = require("fs");
|
|
5050
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
5051
|
+
var import_types21 = require("@neat.is/types");
|
|
4701
5052
|
var SCHEMA_VERSION = 4;
|
|
4702
5053
|
function migrateV1ToV2(payload) {
|
|
4703
5054
|
const nodes = payload.graph.nodes;
|
|
@@ -4719,12 +5070,12 @@ function migrateV2ToV3(payload) {
|
|
|
4719
5070
|
for (const edge of edges) {
|
|
4720
5071
|
const attrs = edge.attributes;
|
|
4721
5072
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
4722
|
-
attrs.provenance =
|
|
5073
|
+
attrs.provenance = import_types21.Provenance.OBSERVED;
|
|
4723
5074
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
4724
5075
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
4725
5076
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
4726
5077
|
if (type && source && target) {
|
|
4727
|
-
const newId = (0,
|
|
5078
|
+
const newId = (0, import_types21.observedEdgeId)(source, target, type);
|
|
4728
5079
|
attrs.id = newId;
|
|
4729
5080
|
if (edge.key) edge.key = newId;
|
|
4730
5081
|
}
|
|
@@ -4733,7 +5084,7 @@ function migrateV2ToV3(payload) {
|
|
|
4733
5084
|
return { ...payload, schemaVersion: 3 };
|
|
4734
5085
|
}
|
|
4735
5086
|
async function ensureDir(filePath) {
|
|
4736
|
-
await
|
|
5087
|
+
await import_node_fs19.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
|
|
4737
5088
|
}
|
|
4738
5089
|
async function saveGraphToDisk(graph, outPath) {
|
|
4739
5090
|
await ensureDir(outPath);
|
|
@@ -4743,13 +5094,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
4743
5094
|
graph: graph.export()
|
|
4744
5095
|
};
|
|
4745
5096
|
const tmp = `${outPath}.tmp`;
|
|
4746
|
-
await
|
|
4747
|
-
await
|
|
5097
|
+
await import_node_fs19.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
5098
|
+
await import_node_fs19.promises.rename(tmp, outPath);
|
|
4748
5099
|
}
|
|
4749
5100
|
async function loadGraphFromDisk(graph, outPath) {
|
|
4750
5101
|
let raw;
|
|
4751
5102
|
try {
|
|
4752
|
-
raw = await
|
|
5103
|
+
raw = await import_node_fs19.promises.readFile(outPath, "utf8");
|
|
4753
5104
|
} catch (err) {
|
|
4754
5105
|
if (err.code === "ENOENT") return;
|
|
4755
5106
|
throw err;
|
|
@@ -4810,11 +5161,326 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
4810
5161
|
init_cjs_shims();
|
|
4811
5162
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
4812
5163
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4813
|
-
var
|
|
5164
|
+
var import_types24 = require("@neat.is/types");
|
|
5165
|
+
|
|
5166
|
+
// src/extend/index.ts
|
|
5167
|
+
init_cjs_shims();
|
|
5168
|
+
var import_node_fs21 = require("fs");
|
|
5169
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5170
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
5171
|
+
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
5172
|
+
|
|
5173
|
+
// src/installers/package-manager.ts
|
|
5174
|
+
init_cjs_shims();
|
|
5175
|
+
var import_node_fs20 = require("fs");
|
|
5176
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
5177
|
+
var import_node_child_process = require("child_process");
|
|
5178
|
+
var LOCKFILE_PRIORITY = [
|
|
5179
|
+
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
5180
|
+
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
5181
|
+
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
5182
|
+
{
|
|
5183
|
+
lockfile: "package-lock.json",
|
|
5184
|
+
pm: "npm",
|
|
5185
|
+
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
5186
|
+
}
|
|
5187
|
+
];
|
|
5188
|
+
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
5189
|
+
async function exists2(p) {
|
|
5190
|
+
try {
|
|
5191
|
+
await import_node_fs20.promises.access(p);
|
|
5192
|
+
return true;
|
|
5193
|
+
} catch {
|
|
5194
|
+
return false;
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
async function detectPackageManager(serviceDir) {
|
|
5198
|
+
let dir = import_node_path34.default.resolve(serviceDir);
|
|
5199
|
+
const stops = /* @__PURE__ */ new Set();
|
|
5200
|
+
for (let i = 0; i < 64; i++) {
|
|
5201
|
+
if (stops.has(dir)) break;
|
|
5202
|
+
stops.add(dir);
|
|
5203
|
+
for (const candidate of LOCKFILE_PRIORITY) {
|
|
5204
|
+
const lockPath = import_node_path34.default.join(dir, candidate.lockfile);
|
|
5205
|
+
if (await exists2(lockPath)) {
|
|
5206
|
+
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
5207
|
+
}
|
|
5208
|
+
}
|
|
5209
|
+
const parent = import_node_path34.default.dirname(dir);
|
|
5210
|
+
if (parent === dir) break;
|
|
5211
|
+
dir = parent;
|
|
5212
|
+
}
|
|
5213
|
+
return { pm: "npm", cwd: import_node_path34.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
5214
|
+
}
|
|
5215
|
+
async function runPackageManagerInstall(cmd) {
|
|
5216
|
+
return new Promise((resolve) => {
|
|
5217
|
+
const child = (0, import_node_child_process.spawn)(cmd.pm, cmd.args, {
|
|
5218
|
+
cwd: cmd.cwd,
|
|
5219
|
+
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
5220
|
+
env: process.env,
|
|
5221
|
+
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
5222
|
+
// exits before install finishes. Cross-platform-safe.
|
|
5223
|
+
shell: false,
|
|
5224
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
5225
|
+
});
|
|
5226
|
+
let stderr = "";
|
|
5227
|
+
child.stderr?.on("data", (chunk) => {
|
|
5228
|
+
stderr += chunk.toString("utf8");
|
|
5229
|
+
});
|
|
5230
|
+
child.on("error", (err) => {
|
|
5231
|
+
resolve({
|
|
5232
|
+
pm: cmd.pm,
|
|
5233
|
+
cwd: cmd.cwd,
|
|
5234
|
+
args: cmd.args,
|
|
5235
|
+
exitCode: 127,
|
|
5236
|
+
stderr: stderr + `
|
|
5237
|
+
${err.message}`
|
|
5238
|
+
});
|
|
5239
|
+
});
|
|
5240
|
+
child.on("close", (code) => {
|
|
5241
|
+
resolve({
|
|
5242
|
+
pm: cmd.pm,
|
|
5243
|
+
cwd: cmd.cwd,
|
|
5244
|
+
args: cmd.args,
|
|
5245
|
+
exitCode: code ?? 1,
|
|
5246
|
+
stderr: stderr.trim()
|
|
5247
|
+
});
|
|
5248
|
+
});
|
|
5249
|
+
});
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5252
|
+
// src/extend/index.ts
|
|
5253
|
+
async function fileExists2(p) {
|
|
5254
|
+
try {
|
|
5255
|
+
await import_node_fs21.promises.access(p);
|
|
5256
|
+
return true;
|
|
5257
|
+
} catch {
|
|
5258
|
+
return false;
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5261
|
+
async function readPackageJson(scanPath) {
|
|
5262
|
+
const pkgPath = import_node_path35.default.join(scanPath, "package.json");
|
|
5263
|
+
const raw = await import_node_fs21.promises.readFile(pkgPath, "utf8");
|
|
5264
|
+
return JSON.parse(raw);
|
|
5265
|
+
}
|
|
5266
|
+
async function findHookFiles(scanPath) {
|
|
5267
|
+
const entries = await import_node_fs21.promises.readdir(scanPath);
|
|
5268
|
+
return entries.filter(
|
|
5269
|
+
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
5270
|
+
).sort();
|
|
5271
|
+
}
|
|
5272
|
+
function extendLogPath() {
|
|
5273
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path35.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
5274
|
+
}
|
|
5275
|
+
async function appendExtendLog(entry) {
|
|
5276
|
+
const logPath = extendLogPath();
|
|
5277
|
+
await import_node_fs21.promises.mkdir(import_node_path35.default.dirname(logPath), { recursive: true });
|
|
5278
|
+
await import_node_fs21.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
5279
|
+
}
|
|
5280
|
+
function splicedContent(fileContent, snippet2) {
|
|
5281
|
+
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
5282
|
+
return fileContent.replace("__INSTRUMENTATION_BLOCK__", `${snippet2}
|
|
5283
|
+
__INSTRUMENTATION_BLOCK__`);
|
|
5284
|
+
}
|
|
5285
|
+
const lines = fileContent.split("\n");
|
|
5286
|
+
let lastPushIdx = -1;
|
|
5287
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5288
|
+
if (lines[i].includes("instrumentations.push(")) lastPushIdx = i;
|
|
5289
|
+
}
|
|
5290
|
+
if (lastPushIdx >= 0) {
|
|
5291
|
+
lines.splice(lastPushIdx + 1, 0, snippet2);
|
|
5292
|
+
return lines.join("\n");
|
|
5293
|
+
}
|
|
5294
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5295
|
+
if (lines[i].includes("new NodeSDK(")) {
|
|
5296
|
+
lines.splice(i, 0, snippet2);
|
|
5297
|
+
return lines.join("\n");
|
|
5298
|
+
}
|
|
5299
|
+
}
|
|
5300
|
+
return null;
|
|
5301
|
+
}
|
|
5302
|
+
async function listUninstrumented(ctx) {
|
|
5303
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5304
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5305
|
+
const results = [];
|
|
5306
|
+
for (const [library, installedVersion] of Object.entries(allDeps)) {
|
|
5307
|
+
const entry = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
5308
|
+
if (!entry) continue;
|
|
5309
|
+
if (entry.coverage === "bundled" || entry.coverage === "http-only") continue;
|
|
5310
|
+
results.push({
|
|
5311
|
+
library,
|
|
5312
|
+
coverage: entry.coverage,
|
|
5313
|
+
installedVersion,
|
|
5314
|
+
instrumentation_package: entry.instrumentation_package,
|
|
5315
|
+
package_version: entry.package_version,
|
|
5316
|
+
registration: entry.registration,
|
|
5317
|
+
notes: entry.notes
|
|
5318
|
+
});
|
|
5319
|
+
}
|
|
5320
|
+
return results;
|
|
5321
|
+
}
|
|
5322
|
+
function lookupInstrumentation(library, installedVersion) {
|
|
5323
|
+
const entry = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
5324
|
+
if (!entry) return null;
|
|
5325
|
+
return {
|
|
5326
|
+
library: entry.library,
|
|
5327
|
+
coverage: entry.coverage,
|
|
5328
|
+
instrumentation_package: entry.instrumentation_package,
|
|
5329
|
+
package_version: entry.package_version,
|
|
5330
|
+
registration: entry.registration,
|
|
5331
|
+
notes: entry.notes
|
|
5332
|
+
};
|
|
5333
|
+
}
|
|
5334
|
+
async function describeProjectInstrumentation(ctx) {
|
|
5335
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5336
|
+
const envNeat = await fileExists2(import_node_path35.default.join(ctx.scanPath, ".env.neat"));
|
|
5337
|
+
const registryInstrPackages = new Set(
|
|
5338
|
+
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
5339
|
+
);
|
|
5340
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5341
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5342
|
+
const installedDeps = {};
|
|
5343
|
+
for (const [key, version] of Object.entries(allDeps)) {
|
|
5344
|
+
if (key.startsWith("@opentelemetry/") || registryInstrPackages.has(key)) {
|
|
5345
|
+
installedDeps[key] = version;
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
return { hookFiles, envNeat, installedDeps };
|
|
5349
|
+
}
|
|
5350
|
+
async function applyExtension(ctx, args, options) {
|
|
5351
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5352
|
+
if (hookFiles.length === 0) {
|
|
5353
|
+
throw new Error(
|
|
5354
|
+
`No instrumentation hook files found in ${ctx.scanPath}. Run \`neat init\` first.`
|
|
5355
|
+
);
|
|
5356
|
+
}
|
|
5357
|
+
for (const file of hookFiles) {
|
|
5358
|
+
const content = await import_node_fs21.promises.readFile(import_node_path35.default.join(ctx.scanPath, file), "utf8");
|
|
5359
|
+
if (content.includes(args.registration_snippet)) {
|
|
5360
|
+
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
5361
|
+
}
|
|
5362
|
+
}
|
|
5363
|
+
const primaryFile = hookFiles[0];
|
|
5364
|
+
const primaryPath = import_node_path35.default.join(ctx.scanPath, primaryFile);
|
|
5365
|
+
const filesTouched = [];
|
|
5366
|
+
const depsAdded = [];
|
|
5367
|
+
const pkgPath = import_node_path35.default.join(ctx.scanPath, "package.json");
|
|
5368
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5369
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5370
|
+
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
5371
|
+
await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5372
|
+
filesTouched.push("package.json");
|
|
5373
|
+
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
5374
|
+
}
|
|
5375
|
+
const hookContent = await import_node_fs21.promises.readFile(primaryPath, "utf8");
|
|
5376
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5377
|
+
if (!patched) {
|
|
5378
|
+
throw new Error(
|
|
5379
|
+
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
5380
|
+
);
|
|
5381
|
+
}
|
|
5382
|
+
await import_node_fs21.promises.writeFile(primaryPath, patched, "utf8");
|
|
5383
|
+
filesTouched.push(primaryFile);
|
|
5384
|
+
const cmd = await detectPackageManager(ctx.scanPath);
|
|
5385
|
+
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
5386
|
+
const install = await installer(cmd);
|
|
5387
|
+
const installOutput = install.exitCode === 0 ? `${cmd.pm} install succeeded` : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`;
|
|
5388
|
+
await appendExtendLog({
|
|
5389
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5390
|
+
project: ctx.project,
|
|
5391
|
+
library: args.library,
|
|
5392
|
+
instrumentation_package: args.instrumentation_package,
|
|
5393
|
+
version: args.version,
|
|
5394
|
+
registration_snippet: args.registration_snippet,
|
|
5395
|
+
filesTouched,
|
|
5396
|
+
depsAdded,
|
|
5397
|
+
installOutput
|
|
5398
|
+
});
|
|
5399
|
+
return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false };
|
|
5400
|
+
}
|
|
5401
|
+
async function dryRunExtension(ctx, args) {
|
|
5402
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5403
|
+
if (hookFiles.length === 0) {
|
|
5404
|
+
return {
|
|
5405
|
+
library: args.library,
|
|
5406
|
+
filesTouched: [],
|
|
5407
|
+
depsToAdd: [],
|
|
5408
|
+
packageJsonPatch: {},
|
|
5409
|
+
templatePatch: "No hook files found. Run 'neat init' first."
|
|
5410
|
+
};
|
|
5411
|
+
}
|
|
5412
|
+
for (const file of hookFiles) {
|
|
5413
|
+
const content = await import_node_fs21.promises.readFile(import_node_path35.default.join(ctx.scanPath, file), "utf8");
|
|
5414
|
+
if (content.includes(args.registration_snippet)) {
|
|
5415
|
+
return {
|
|
5416
|
+
library: args.library,
|
|
5417
|
+
filesTouched: [],
|
|
5418
|
+
depsToAdd: [],
|
|
5419
|
+
packageJsonPatch: {},
|
|
5420
|
+
templatePatch: "Already applied \u2014 no changes would be made."
|
|
5421
|
+
};
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
const primaryFile = hookFiles[0];
|
|
5425
|
+
const filesTouched = [];
|
|
5426
|
+
const depsToAdd = [];
|
|
5427
|
+
let packageJsonPatch = {};
|
|
5428
|
+
let templatePatch = "";
|
|
5429
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5430
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5431
|
+
packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } };
|
|
5432
|
+
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
5433
|
+
filesTouched.push("package.json");
|
|
5434
|
+
}
|
|
5435
|
+
const hookContent = await import_node_fs21.promises.readFile(import_node_path35.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
5436
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5437
|
+
if (patched) {
|
|
5438
|
+
filesTouched.push(primaryFile);
|
|
5439
|
+
templatePatch = `+ ${args.registration_snippet}`;
|
|
5440
|
+
} else {
|
|
5441
|
+
templatePatch = "Could not find insertion point in hook file.";
|
|
5442
|
+
}
|
|
5443
|
+
return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch };
|
|
5444
|
+
}
|
|
5445
|
+
async function rollbackExtension(ctx, args) {
|
|
5446
|
+
const logPath = extendLogPath();
|
|
5447
|
+
if (!await fileExists2(logPath)) {
|
|
5448
|
+
return { undone: false, message: "no apply found for library" };
|
|
5449
|
+
}
|
|
5450
|
+
const raw = await import_node_fs21.promises.readFile(logPath, "utf8");
|
|
5451
|
+
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
5452
|
+
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
5453
|
+
if (!match) {
|
|
5454
|
+
return { undone: false, message: "no apply found for library" };
|
|
5455
|
+
}
|
|
5456
|
+
const pkgPath = import_node_path35.default.join(ctx.scanPath, "package.json");
|
|
5457
|
+
if (await fileExists2(pkgPath)) {
|
|
5458
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5459
|
+
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
5460
|
+
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
5461
|
+
pkg.dependencies = rest;
|
|
5462
|
+
await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5463
|
+
}
|
|
5464
|
+
}
|
|
5465
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5466
|
+
for (const file of hookFiles) {
|
|
5467
|
+
const filePath = import_node_path35.default.join(ctx.scanPath, file);
|
|
5468
|
+
const content = await import_node_fs21.promises.readFile(filePath, "utf8");
|
|
5469
|
+
if (content.includes(match.registration_snippet)) {
|
|
5470
|
+
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
5471
|
+
await import_node_fs21.promises.writeFile(filePath, filtered, "utf8");
|
|
5472
|
+
break;
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
5475
|
+
return {
|
|
5476
|
+
undone: true,
|
|
5477
|
+
message: `rolled back ${match.library} (${match.instrumentation_package})`
|
|
5478
|
+
};
|
|
5479
|
+
}
|
|
4814
5480
|
|
|
4815
5481
|
// src/divergences.ts
|
|
4816
5482
|
init_cjs_shims();
|
|
4817
|
-
var
|
|
5483
|
+
var import_types22 = require("@neat.is/types");
|
|
4818
5484
|
function bucketKey(source, target, type) {
|
|
4819
5485
|
return `${type}|${source}|${target}`;
|
|
4820
5486
|
}
|
|
@@ -4822,22 +5488,22 @@ function bucketEdges(graph) {
|
|
|
4822
5488
|
const buckets = /* @__PURE__ */ new Map();
|
|
4823
5489
|
graph.forEachEdge((id, attrs) => {
|
|
4824
5490
|
const e = attrs;
|
|
4825
|
-
const parsed = (0,
|
|
5491
|
+
const parsed = (0, import_types22.parseEdgeId)(id);
|
|
4826
5492
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
4827
5493
|
const key = bucketKey(e.source, e.target, e.type);
|
|
4828
5494
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
4829
5495
|
switch (provenance) {
|
|
4830
|
-
case
|
|
5496
|
+
case import_types22.Provenance.EXTRACTED:
|
|
4831
5497
|
cur.extracted = e;
|
|
4832
5498
|
break;
|
|
4833
|
-
case
|
|
5499
|
+
case import_types22.Provenance.OBSERVED:
|
|
4834
5500
|
cur.observed = e;
|
|
4835
5501
|
break;
|
|
4836
|
-
case
|
|
5502
|
+
case import_types22.Provenance.INFERRED:
|
|
4837
5503
|
cur.inferred = e;
|
|
4838
5504
|
break;
|
|
4839
5505
|
default:
|
|
4840
|
-
if (e.provenance ===
|
|
5506
|
+
if (e.provenance === import_types22.Provenance.STALE) cur.stale = e;
|
|
4841
5507
|
}
|
|
4842
5508
|
buckets.set(key, cur);
|
|
4843
5509
|
});
|
|
@@ -4846,7 +5512,7 @@ function bucketEdges(graph) {
|
|
|
4846
5512
|
function nodeIsFrontier(graph, nodeId) {
|
|
4847
5513
|
if (!graph.hasNode(nodeId)) return false;
|
|
4848
5514
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4849
|
-
return attrs.type ===
|
|
5515
|
+
return attrs.type === import_types22.NodeType.FrontierNode;
|
|
4850
5516
|
}
|
|
4851
5517
|
function clampConfidence(n) {
|
|
4852
5518
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -4867,7 +5533,7 @@ function gradedConfidence(edge) {
|
|
|
4867
5533
|
}
|
|
4868
5534
|
function detectMissingDivergences(graph, bucket) {
|
|
4869
5535
|
const out = [];
|
|
4870
|
-
if (bucket.type ===
|
|
5536
|
+
if (bucket.type === import_types22.EdgeType.CONTAINS) return out;
|
|
4871
5537
|
if (bucket.extracted && !bucket.observed) {
|
|
4872
5538
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
4873
5539
|
out.push({
|
|
@@ -4908,7 +5574,7 @@ function declaredHostFor(svc) {
|
|
|
4908
5574
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4909
5575
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4910
5576
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4911
|
-
if (e.type ===
|
|
5577
|
+
if (e.type === import_types22.EdgeType.CONFIGURED_BY && e.provenance === import_types22.Provenance.EXTRACTED) {
|
|
4912
5578
|
return true;
|
|
4913
5579
|
}
|
|
4914
5580
|
}
|
|
@@ -4921,10 +5587,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4921
5587
|
const out = [];
|
|
4922
5588
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4923
5589
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4924
|
-
if (edge.type !==
|
|
4925
|
-
if (edge.provenance !==
|
|
5590
|
+
if (edge.type !== import_types22.EdgeType.CONNECTS_TO) continue;
|
|
5591
|
+
if (edge.provenance !== import_types22.Provenance.OBSERVED) continue;
|
|
4926
5592
|
const target = graph.getNodeAttributes(edge.target);
|
|
4927
|
-
if (target.type !==
|
|
5593
|
+
if (target.type !== import_types22.NodeType.DatabaseNode) continue;
|
|
4928
5594
|
const observedHost = target.host?.trim();
|
|
4929
5595
|
if (!observedHost) continue;
|
|
4930
5596
|
if (observedHost === declaredHost) continue;
|
|
@@ -4946,10 +5612,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4946
5612
|
const deps = svc.dependencies ?? {};
|
|
4947
5613
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4948
5614
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4949
|
-
if (edge.type !==
|
|
4950
|
-
if (edge.provenance !==
|
|
5615
|
+
if (edge.type !== import_types22.EdgeType.CONNECTS_TO) continue;
|
|
5616
|
+
if (edge.provenance !== import_types22.Provenance.OBSERVED) continue;
|
|
4951
5617
|
const target = graph.getNodeAttributes(edge.target);
|
|
4952
|
-
if (target.type !==
|
|
5618
|
+
if (target.type !== import_types22.NodeType.DatabaseNode) continue;
|
|
4953
5619
|
for (const pair of compatPairs()) {
|
|
4954
5620
|
if (pair.engine !== target.engine) continue;
|
|
4955
5621
|
const declared = deps[pair.driver];
|
|
@@ -5010,7 +5676,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
5010
5676
|
}
|
|
5011
5677
|
graph.forEachNode((nodeId, attrs) => {
|
|
5012
5678
|
const n = attrs;
|
|
5013
|
-
if (n.type !==
|
|
5679
|
+
if (n.type !== import_types22.NodeType.ServiceNode) return;
|
|
5014
5680
|
const svc = n;
|
|
5015
5681
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
5016
5682
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -5043,7 +5709,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
5043
5709
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
5044
5710
|
return a.target.localeCompare(b.target);
|
|
5045
5711
|
});
|
|
5046
|
-
return
|
|
5712
|
+
return import_types22.DivergenceResultSchema.parse({
|
|
5047
5713
|
divergences: filtered,
|
|
5048
5714
|
totalAffected: filtered.length,
|
|
5049
5715
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5052,7 +5718,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
5052
5718
|
|
|
5053
5719
|
// src/diff.ts
|
|
5054
5720
|
init_cjs_shims();
|
|
5055
|
-
var
|
|
5721
|
+
var import_node_fs22 = require("fs");
|
|
5056
5722
|
async function loadSnapshotForDiff(target) {
|
|
5057
5723
|
if (/^https?:\/\//i.test(target)) {
|
|
5058
5724
|
const res = await fetch(target);
|
|
@@ -5061,7 +5727,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
5061
5727
|
}
|
|
5062
5728
|
return await res.json();
|
|
5063
5729
|
}
|
|
5064
|
-
const raw = await
|
|
5730
|
+
const raw = await import_node_fs22.promises.readFile(target, "utf8");
|
|
5065
5731
|
return JSON.parse(raw);
|
|
5066
5732
|
}
|
|
5067
5733
|
function indexEntries(entries) {
|
|
@@ -5129,23 +5795,23 @@ function canonicalJson(value) {
|
|
|
5129
5795
|
|
|
5130
5796
|
// src/projects.ts
|
|
5131
5797
|
init_cjs_shims();
|
|
5132
|
-
var
|
|
5798
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
5133
5799
|
function pathsForProject(project, baseDir) {
|
|
5134
5800
|
if (project === DEFAULT_PROJECT) {
|
|
5135
5801
|
return {
|
|
5136
|
-
snapshotPath:
|
|
5137
|
-
errorsPath:
|
|
5138
|
-
staleEventsPath:
|
|
5139
|
-
embeddingsCachePath:
|
|
5140
|
-
policyViolationsPath:
|
|
5802
|
+
snapshotPath: import_node_path36.default.join(baseDir, "graph.json"),
|
|
5803
|
+
errorsPath: import_node_path36.default.join(baseDir, "errors.ndjson"),
|
|
5804
|
+
staleEventsPath: import_node_path36.default.join(baseDir, "stale-events.ndjson"),
|
|
5805
|
+
embeddingsCachePath: import_node_path36.default.join(baseDir, "embeddings.json"),
|
|
5806
|
+
policyViolationsPath: import_node_path36.default.join(baseDir, "policy-violations.ndjson")
|
|
5141
5807
|
};
|
|
5142
5808
|
}
|
|
5143
5809
|
return {
|
|
5144
|
-
snapshotPath:
|
|
5145
|
-
errorsPath:
|
|
5146
|
-
staleEventsPath:
|
|
5147
|
-
embeddingsCachePath:
|
|
5148
|
-
policyViolationsPath:
|
|
5810
|
+
snapshotPath: import_node_path36.default.join(baseDir, `${project}.json`),
|
|
5811
|
+
errorsPath: import_node_path36.default.join(baseDir, `errors.${project}.ndjson`),
|
|
5812
|
+
staleEventsPath: import_node_path36.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
5813
|
+
embeddingsCachePath: import_node_path36.default.join(baseDir, `embeddings.${project}.json`),
|
|
5814
|
+
policyViolationsPath: import_node_path36.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
5149
5815
|
};
|
|
5150
5816
|
}
|
|
5151
5817
|
var Projects = class {
|
|
@@ -5181,25 +5847,25 @@ var Projects = class {
|
|
|
5181
5847
|
|
|
5182
5848
|
// src/registry.ts
|
|
5183
5849
|
init_cjs_shims();
|
|
5184
|
-
var
|
|
5185
|
-
var
|
|
5186
|
-
var
|
|
5187
|
-
var
|
|
5850
|
+
var import_node_fs23 = require("fs");
|
|
5851
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
5852
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
5853
|
+
var import_types23 = require("@neat.is/types");
|
|
5188
5854
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
5189
5855
|
var LOCK_RETRY_MS = 50;
|
|
5190
5856
|
function neatHome() {
|
|
5191
5857
|
const override = process.env.NEAT_HOME;
|
|
5192
|
-
if (override && override.length > 0) return
|
|
5193
|
-
return
|
|
5858
|
+
if (override && override.length > 0) return import_node_path37.default.resolve(override);
|
|
5859
|
+
return import_node_path37.default.join(import_node_os3.default.homedir(), ".neat");
|
|
5194
5860
|
}
|
|
5195
5861
|
function registryPath() {
|
|
5196
|
-
return
|
|
5862
|
+
return import_node_path37.default.join(neatHome(), "projects.json");
|
|
5197
5863
|
}
|
|
5198
5864
|
function registryLockPath() {
|
|
5199
|
-
return
|
|
5865
|
+
return import_node_path37.default.join(neatHome(), "projects.json.lock");
|
|
5200
5866
|
}
|
|
5201
5867
|
function daemonPidPath() {
|
|
5202
|
-
return
|
|
5868
|
+
return import_node_path37.default.join(neatHome(), "neatd.pid");
|
|
5203
5869
|
}
|
|
5204
5870
|
function isPidAliveDefault(pid) {
|
|
5205
5871
|
try {
|
|
@@ -5211,7 +5877,7 @@ function isPidAliveDefault(pid) {
|
|
|
5211
5877
|
}
|
|
5212
5878
|
async function readPidFile(file) {
|
|
5213
5879
|
try {
|
|
5214
|
-
const raw = await
|
|
5880
|
+
const raw = await import_node_fs23.promises.readFile(file, "utf8");
|
|
5215
5881
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
5216
5882
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
5217
5883
|
} catch {
|
|
@@ -5259,32 +5925,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
5259
5925
|
}
|
|
5260
5926
|
}
|
|
5261
5927
|
async function normalizeProjectPath(input) {
|
|
5262
|
-
const resolved =
|
|
5928
|
+
const resolved = import_node_path37.default.resolve(input);
|
|
5263
5929
|
try {
|
|
5264
|
-
return await
|
|
5930
|
+
return await import_node_fs23.promises.realpath(resolved);
|
|
5265
5931
|
} catch {
|
|
5266
5932
|
return resolved;
|
|
5267
5933
|
}
|
|
5268
5934
|
}
|
|
5269
5935
|
async function writeAtomically(target, contents) {
|
|
5270
|
-
await
|
|
5936
|
+
await import_node_fs23.promises.mkdir(import_node_path37.default.dirname(target), { recursive: true });
|
|
5271
5937
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
5272
|
-
const fd = await
|
|
5938
|
+
const fd = await import_node_fs23.promises.open(tmp, "w");
|
|
5273
5939
|
try {
|
|
5274
5940
|
await fd.writeFile(contents, "utf8");
|
|
5275
5941
|
await fd.sync();
|
|
5276
5942
|
} finally {
|
|
5277
5943
|
await fd.close();
|
|
5278
5944
|
}
|
|
5279
|
-
await
|
|
5945
|
+
await import_node_fs23.promises.rename(tmp, target);
|
|
5280
5946
|
}
|
|
5281
5947
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
5282
5948
|
const deadline = Date.now() + timeoutMs;
|
|
5283
|
-
await
|
|
5949
|
+
await import_node_fs23.promises.mkdir(import_node_path37.default.dirname(lockPath), { recursive: true });
|
|
5284
5950
|
let probedHolder = false;
|
|
5285
5951
|
while (true) {
|
|
5286
5952
|
try {
|
|
5287
|
-
const fd = await
|
|
5953
|
+
const fd = await import_node_fs23.promises.open(lockPath, "wx");
|
|
5288
5954
|
try {
|
|
5289
5955
|
await fd.writeFile(`${process.pid}
|
|
5290
5956
|
`, "utf8");
|
|
@@ -5309,7 +5975,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
5309
5975
|
}
|
|
5310
5976
|
}
|
|
5311
5977
|
async function releaseLock(lockPath) {
|
|
5312
|
-
await
|
|
5978
|
+
await import_node_fs23.promises.unlink(lockPath).catch(() => {
|
|
5313
5979
|
});
|
|
5314
5980
|
}
|
|
5315
5981
|
async function withLock(fn) {
|
|
@@ -5325,7 +5991,7 @@ async function readRegistry() {
|
|
|
5325
5991
|
const file = registryPath();
|
|
5326
5992
|
let raw;
|
|
5327
5993
|
try {
|
|
5328
|
-
raw = await
|
|
5994
|
+
raw = await import_node_fs23.promises.readFile(file, "utf8");
|
|
5329
5995
|
} catch (err) {
|
|
5330
5996
|
if (err.code === "ENOENT") {
|
|
5331
5997
|
return { version: 1, projects: [] };
|
|
@@ -5333,10 +5999,10 @@ async function readRegistry() {
|
|
|
5333
5999
|
throw err;
|
|
5334
6000
|
}
|
|
5335
6001
|
const parsed = JSON.parse(raw);
|
|
5336
|
-
return
|
|
6002
|
+
return import_types23.RegistryFileSchema.parse(parsed);
|
|
5337
6003
|
}
|
|
5338
6004
|
async function writeRegistry(reg) {
|
|
5339
|
-
const validated =
|
|
6005
|
+
const validated = import_types23.RegistryFileSchema.parse(reg);
|
|
5340
6006
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
5341
6007
|
}
|
|
5342
6008
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -5608,11 +6274,11 @@ function registerRoutes(scope, ctx) {
|
|
|
5608
6274
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
5609
6275
|
const parsed = [];
|
|
5610
6276
|
for (const c of candidates) {
|
|
5611
|
-
const r =
|
|
6277
|
+
const r = import_types24.DivergenceTypeSchema.safeParse(c);
|
|
5612
6278
|
if (!r.success) {
|
|
5613
6279
|
return reply.code(400).send({
|
|
5614
6280
|
error: `unknown divergence type "${c}"`,
|
|
5615
|
-
allowed:
|
|
6281
|
+
allowed: import_types24.DivergenceTypeSchema.options
|
|
5616
6282
|
});
|
|
5617
6283
|
}
|
|
5618
6284
|
parsed.push(r.data);
|
|
@@ -5825,7 +6491,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5825
6491
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
5826
6492
|
let violations = await log.readAll();
|
|
5827
6493
|
if (req.query.severity) {
|
|
5828
|
-
const sev =
|
|
6494
|
+
const sev = import_types24.PolicySeveritySchema.safeParse(req.query.severity);
|
|
5829
6495
|
if (!sev.success) {
|
|
5830
6496
|
return reply.code(400).send({
|
|
5831
6497
|
error: "invalid severity",
|
|
@@ -5842,7 +6508,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5842
6508
|
scope.post("/policies/check", async (req, reply) => {
|
|
5843
6509
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5844
6510
|
if (!proj) return;
|
|
5845
|
-
const parsed =
|
|
6511
|
+
const parsed = import_types24.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
5846
6512
|
if (!parsed.success) {
|
|
5847
6513
|
return reply.code(400).send({
|
|
5848
6514
|
error: "invalid /policies/check body",
|
|
@@ -5875,6 +6541,105 @@ function registerRoutes(scope, ctx) {
|
|
|
5875
6541
|
violations
|
|
5876
6542
|
};
|
|
5877
6543
|
});
|
|
6544
|
+
scope.get("/extend/list-uninstrumented", async (req, reply) => {
|
|
6545
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6546
|
+
if (!proj) return;
|
|
6547
|
+
if (!proj.scanPath) {
|
|
6548
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6549
|
+
}
|
|
6550
|
+
try {
|
|
6551
|
+
const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
6552
|
+
return { libraries: results };
|
|
6553
|
+
} catch (err) {
|
|
6554
|
+
return reply.code(500).send({ error: err.message });
|
|
6555
|
+
}
|
|
6556
|
+
});
|
|
6557
|
+
scope.get("/extend/lookup", async (req, reply) => {
|
|
6558
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6559
|
+
if (!proj) return;
|
|
6560
|
+
const { library, version } = req.query;
|
|
6561
|
+
if (!library) {
|
|
6562
|
+
return reply.code(400).send({ error: "query parameter `library` is required" });
|
|
6563
|
+
}
|
|
6564
|
+
const result = lookupInstrumentation(library, version);
|
|
6565
|
+
if (!result) {
|
|
6566
|
+
return reply.code(404).send({ error: "library not found in registry", library });
|
|
6567
|
+
}
|
|
6568
|
+
return result;
|
|
6569
|
+
});
|
|
6570
|
+
scope.get("/extend/describe", async (req, reply) => {
|
|
6571
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6572
|
+
if (!proj) return;
|
|
6573
|
+
if (!proj.scanPath) {
|
|
6574
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6575
|
+
}
|
|
6576
|
+
try {
|
|
6577
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
6578
|
+
return state;
|
|
6579
|
+
} catch (err) {
|
|
6580
|
+
return reply.code(500).send({ error: err.message });
|
|
6581
|
+
}
|
|
6582
|
+
});
|
|
6583
|
+
scope.post("/extend/apply", async (req, reply) => {
|
|
6584
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6585
|
+
if (!proj) return;
|
|
6586
|
+
if (!proj.scanPath) {
|
|
6587
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6588
|
+
}
|
|
6589
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6590
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6591
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6592
|
+
}
|
|
6593
|
+
try {
|
|
6594
|
+
const result = await applyExtension(
|
|
6595
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6596
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6597
|
+
);
|
|
6598
|
+
return result;
|
|
6599
|
+
} catch (err) {
|
|
6600
|
+
return reply.code(500).send({ error: err.message });
|
|
6601
|
+
}
|
|
6602
|
+
});
|
|
6603
|
+
scope.post("/extend/dry-run", async (req, reply) => {
|
|
6604
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6605
|
+
if (!proj) return;
|
|
6606
|
+
if (!proj.scanPath) {
|
|
6607
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6608
|
+
}
|
|
6609
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6610
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6611
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6612
|
+
}
|
|
6613
|
+
try {
|
|
6614
|
+
const result = await dryRunExtension(
|
|
6615
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6616
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6617
|
+
);
|
|
6618
|
+
return result;
|
|
6619
|
+
} catch (err) {
|
|
6620
|
+
return reply.code(500).send({ error: err.message });
|
|
6621
|
+
}
|
|
6622
|
+
});
|
|
6623
|
+
scope.post("/extend/rollback", async (req, reply) => {
|
|
6624
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6625
|
+
if (!proj) return;
|
|
6626
|
+
if (!proj.scanPath) {
|
|
6627
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6628
|
+
}
|
|
6629
|
+
const { library } = req.body ?? {};
|
|
6630
|
+
if (!library) {
|
|
6631
|
+
return reply.code(400).send({ error: "body must include library" });
|
|
6632
|
+
}
|
|
6633
|
+
try {
|
|
6634
|
+
const result = await rollbackExtension(
|
|
6635
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6636
|
+
{ library }
|
|
6637
|
+
);
|
|
6638
|
+
return result;
|
|
6639
|
+
} catch (err) {
|
|
6640
|
+
return reply.code(500).send({ error: err.message });
|
|
6641
|
+
}
|
|
6642
|
+
});
|
|
5878
6643
|
}
|
|
5879
6644
|
async function buildApi(opts) {
|
|
5880
6645
|
const app = (0, import_fastify.default)({ logger: false });
|
|
@@ -5985,15 +6750,15 @@ init_otel_grpc();
|
|
|
5985
6750
|
|
|
5986
6751
|
// src/daemon.ts
|
|
5987
6752
|
init_cjs_shims();
|
|
5988
|
-
var
|
|
5989
|
-
var
|
|
6753
|
+
var import_node_fs25 = require("fs");
|
|
6754
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
5990
6755
|
init_otel();
|
|
5991
6756
|
init_auth();
|
|
5992
6757
|
|
|
5993
6758
|
// src/unrouted.ts
|
|
5994
6759
|
init_cjs_shims();
|
|
5995
|
-
var
|
|
5996
|
-
var
|
|
6760
|
+
var import_node_fs24 = require("fs");
|
|
6761
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
5997
6762
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
5998
6763
|
return {
|
|
5999
6764
|
timestamp: now.toISOString(),
|
|
@@ -6003,21 +6768,21 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
6003
6768
|
};
|
|
6004
6769
|
}
|
|
6005
6770
|
async function appendUnroutedSpan(neatHome2, record) {
|
|
6006
|
-
const target =
|
|
6007
|
-
await
|
|
6008
|
-
await
|
|
6771
|
+
const target = import_node_path40.default.join(neatHome2, "errors.ndjson");
|
|
6772
|
+
await import_node_fs24.promises.mkdir(neatHome2, { recursive: true });
|
|
6773
|
+
await import_node_fs24.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
6009
6774
|
}
|
|
6010
6775
|
function unroutedErrorsPath(neatHome2) {
|
|
6011
|
-
return
|
|
6776
|
+
return import_node_path40.default.join(neatHome2, "errors.ndjson");
|
|
6012
6777
|
}
|
|
6013
6778
|
|
|
6014
6779
|
// src/daemon.ts
|
|
6015
6780
|
function neatHomeFor(opts) {
|
|
6016
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
6781
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path41.default.resolve(opts.neatHome);
|
|
6017
6782
|
const env = process.env.NEAT_HOME;
|
|
6018
|
-
if (env && env.length > 0) return
|
|
6783
|
+
if (env && env.length > 0) return import_node_path41.default.resolve(env);
|
|
6019
6784
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
6020
|
-
return
|
|
6785
|
+
return import_node_path41.default.join(home, ".neat");
|
|
6021
6786
|
}
|
|
6022
6787
|
function routeSpanToProject(serviceName, projects) {
|
|
6023
6788
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -6052,9 +6817,9 @@ function isTokenContained(needle, haystack) {
|
|
|
6052
6817
|
return tokens.includes(needle);
|
|
6053
6818
|
}
|
|
6054
6819
|
async function bootstrapProject(entry) {
|
|
6055
|
-
const paths = pathsForProject(entry.name,
|
|
6820
|
+
const paths = pathsForProject(entry.name, import_node_path41.default.join(entry.path, "neat-out"));
|
|
6056
6821
|
try {
|
|
6057
|
-
const stat = await
|
|
6822
|
+
const stat = await import_node_fs25.promises.stat(entry.path);
|
|
6058
6823
|
if (!stat.isDirectory()) {
|
|
6059
6824
|
throw new Error(`registered path ${entry.path} is not a directory`);
|
|
6060
6825
|
}
|
|
@@ -6120,13 +6885,13 @@ async function startDaemon(opts = {}) {
|
|
|
6120
6885
|
const home = neatHomeFor(opts);
|
|
6121
6886
|
const regPath = registryPath();
|
|
6122
6887
|
try {
|
|
6123
|
-
await
|
|
6888
|
+
await import_node_fs25.promises.access(regPath);
|
|
6124
6889
|
} catch {
|
|
6125
6890
|
throw new Error(
|
|
6126
6891
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
6127
6892
|
);
|
|
6128
6893
|
}
|
|
6129
|
-
const pidPath =
|
|
6894
|
+
const pidPath = import_node_path41.default.join(home, "neatd.pid");
|
|
6130
6895
|
await writeAtomically(pidPath, `${process.pid}
|
|
6131
6896
|
`);
|
|
6132
6897
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -6284,7 +7049,7 @@ async function startDaemon(opts = {}) {
|
|
|
6284
7049
|
}
|
|
6285
7050
|
if (restApp) await restApp.close().catch(() => {
|
|
6286
7051
|
});
|
|
6287
|
-
await
|
|
7052
|
+
await import_node_fs25.promises.unlink(pidPath).catch(() => {
|
|
6288
7053
|
});
|
|
6289
7054
|
throw new Error(
|
|
6290
7055
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -6390,7 +7155,7 @@ async function startDaemon(opts = {}) {
|
|
|
6390
7155
|
});
|
|
6391
7156
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
6392
7157
|
});
|
|
6393
|
-
await
|
|
7158
|
+
await import_node_fs25.promises.unlink(pidPath).catch(() => {
|
|
6394
7159
|
});
|
|
6395
7160
|
throw new Error(
|
|
6396
7161
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -6436,9 +7201,9 @@ async function startDaemon(opts = {}) {
|
|
|
6436
7201
|
let registryWatcher = null;
|
|
6437
7202
|
let reloadTimer = null;
|
|
6438
7203
|
try {
|
|
6439
|
-
const regDir =
|
|
6440
|
-
const regBase =
|
|
6441
|
-
registryWatcher = (0,
|
|
7204
|
+
const regDir = import_node_path41.default.dirname(regPath);
|
|
7205
|
+
const regBase = import_node_path41.default.basename(regPath);
|
|
7206
|
+
registryWatcher = (0, import_node_fs25.watch)(regDir, (_eventType, filename) => {
|
|
6442
7207
|
if (filename !== null && filename !== regBase) return;
|
|
6443
7208
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
6444
7209
|
reloadTimer = setTimeout(() => {
|
|
@@ -6481,7 +7246,7 @@ async function startDaemon(opts = {}) {
|
|
|
6481
7246
|
} catch {
|
|
6482
7247
|
}
|
|
6483
7248
|
}
|
|
6484
|
-
await
|
|
7249
|
+
await import_node_fs25.promises.unlink(pidPath).catch(() => {
|
|
6485
7250
|
});
|
|
6486
7251
|
};
|
|
6487
7252
|
return {
|