@neat.is/core 0.4.26-dev.20260702 → 0.4.26-dev.20260703
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-ZX7PCMGZ.js → chunk-2LNICOVU.js} +30 -3
- package/dist/chunk-2LNICOVU.js.map +1 -0
- package/dist/{chunk-XOOCA5T7.js → chunk-4OR4RQEO.js} +13 -1
- package/dist/chunk-4OR4RQEO.js.map +1 -0
- package/dist/{chunk-GAFTW2OX.js → chunk-C5NCCKPZ.js} +2 -2
- package/dist/{chunk-IABNGQT2.js → chunk-S7TDPQDD.js} +15 -3
- package/dist/chunk-S7TDPQDD.js.map +1 -0
- package/dist/{chunk-O25KZNZK.js → chunk-WZYH5DVG.js} +957 -238
- package/dist/chunk-WZYH5DVG.js.map +1 -0
- package/dist/cli.cjs +1149 -406
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +5 -5
- package/dist/index.cjs +1011 -269
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +1112 -278
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-QYHUJ4OY.js → otel-grpc-LFYZDSK3.js} +3 -3
- package/dist/server.cjs +954 -211
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +4 -4
- package/package.json +2 -2
- package/dist/chunk-IABNGQT2.js.map +0 -1
- package/dist/chunk-O25KZNZK.js.map +0 -1
- package/dist/chunk-XOOCA5T7.js.map +0 -1
- package/dist/chunk-ZX7PCMGZ.js.map +0 -1
- /package/dist/{chunk-GAFTW2OX.js.map → chunk-C5NCCKPZ.js.map} +0 -0
- /package/dist/{otel-grpc-QYHUJ4OY.js.map → otel-grpc-LFYZDSK3.js.map} +0 -0
package/dist/neatd.cjs
CHANGED
|
@@ -58,9 +58,9 @@ function mountBearerAuth(app, opts) {
|
|
|
58
58
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
59
59
|
const publicRead = opts.publicRead === true;
|
|
60
60
|
app.addHook("preHandler", (req2, reply, done) => {
|
|
61
|
-
const
|
|
61
|
+
const path47 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
62
62
|
for (const suffix of suffixes) {
|
|
63
|
-
if (
|
|
63
|
+
if (path47 === suffix || path47.endsWith(suffix)) {
|
|
64
64
|
done();
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
@@ -71,11 +71,13 @@ function mountBearerAuth(app, opts) {
|
|
|
71
71
|
}
|
|
72
72
|
const header = req2.headers.authorization;
|
|
73
73
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
74
|
+
opts.onReject?.();
|
|
74
75
|
void reply.code(401).send({ error: "unauthorized" });
|
|
75
76
|
return;
|
|
76
77
|
}
|
|
77
78
|
const provided = Buffer.from(header.slice("Bearer ".length).trim(), "utf8");
|
|
78
79
|
if (provided.length !== expected.length || !(0, import_node_crypto.timingSafeEqual)(provided, expected)) {
|
|
80
|
+
opts.onReject?.();
|
|
79
81
|
void reply.code(401).send({ error: "unauthorized" });
|
|
80
82
|
return;
|
|
81
83
|
}
|
|
@@ -188,8 +190,8 @@ function reshapeGrpcRequest(req2) {
|
|
|
188
190
|
};
|
|
189
191
|
}
|
|
190
192
|
function resolveProtoRoot() {
|
|
191
|
-
const here =
|
|
192
|
-
return
|
|
193
|
+
const here = import_node_path41.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
194
|
+
return import_node_path41.default.resolve(here, "..", "proto");
|
|
193
195
|
}
|
|
194
196
|
function loadTraceService() {
|
|
195
197
|
const protoRoot = resolveProtoRoot();
|
|
@@ -257,13 +259,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
257
259
|
})
|
|
258
260
|
};
|
|
259
261
|
}
|
|
260
|
-
var import_node_url,
|
|
262
|
+
var import_node_url, import_node_path41, import_node_crypto2, grpc, protoLoader;
|
|
261
263
|
var init_otel_grpc = __esm({
|
|
262
264
|
"src/otel-grpc.ts"() {
|
|
263
265
|
"use strict";
|
|
264
266
|
init_cjs_shims();
|
|
265
267
|
import_node_url = require("url");
|
|
266
|
-
|
|
268
|
+
import_node_path41 = __toESM(require("path"), 1);
|
|
267
269
|
import_node_crypto2 = require("crypto");
|
|
268
270
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
269
271
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -336,6 +338,13 @@ function pickEnv(spanAttrs, resourceAttrs) {
|
|
|
336
338
|
}
|
|
337
339
|
return ENV_FALLBACK;
|
|
338
340
|
}
|
|
341
|
+
function messagingDestinationOf(attrs) {
|
|
342
|
+
for (const key of ["messaging.destination.name", "messaging.destination"]) {
|
|
343
|
+
const v = attrs[key];
|
|
344
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
345
|
+
}
|
|
346
|
+
return void 0;
|
|
347
|
+
}
|
|
339
348
|
function parseOtlpRequest(body) {
|
|
340
349
|
const out = [];
|
|
341
350
|
for (const rs of body.resourceSpans ?? []) {
|
|
@@ -362,6 +371,10 @@ function parseOtlpRequest(body) {
|
|
|
362
371
|
attributes: attrs,
|
|
363
372
|
dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
|
|
364
373
|
dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
|
|
374
|
+
messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
|
|
375
|
+
messagingDestination: messagingDestinationOf(attrs),
|
|
376
|
+
graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
|
|
377
|
+
graphqlOperationType: typeof attrs["graphql.operation.type"] === "string" && attrs["graphql.operation.type"].length > 0 ? attrs["graphql.operation.type"] : void 0,
|
|
365
378
|
statusCode: span.status?.code,
|
|
366
379
|
errorMessage: span.status?.message,
|
|
367
380
|
exception: extractExceptionFromEvents(span.events)
|
|
@@ -373,10 +386,10 @@ function parseOtlpRequest(body) {
|
|
|
373
386
|
return out;
|
|
374
387
|
}
|
|
375
388
|
function loadProtoRoot() {
|
|
376
|
-
const here =
|
|
377
|
-
const protoRoot =
|
|
389
|
+
const here = import_node_path42.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
390
|
+
const protoRoot = import_node_path42.default.resolve(here, "..", "proto");
|
|
378
391
|
const root = new import_protobufjs.default.Root();
|
|
379
|
-
root.resolvePath = (_origin, target) =>
|
|
392
|
+
root.resolvePath = (_origin, target) => import_node_path42.default.resolve(protoRoot, target);
|
|
380
393
|
root.loadSync(
|
|
381
394
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
382
395
|
{ keepCase: true }
|
|
@@ -421,7 +434,21 @@ async function buildOtelReceiver(opts) {
|
|
|
421
434
|
logger: false,
|
|
422
435
|
bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024
|
|
423
436
|
});
|
|
424
|
-
|
|
437
|
+
const REJECT_WARN_INTERVAL_MS = 6e4;
|
|
438
|
+
let lastRejectWarnAt = 0;
|
|
439
|
+
const warnRejectedOtlp = () => {
|
|
440
|
+
const now = Date.now();
|
|
441
|
+
if (now - lastRejectWarnAt < REJECT_WARN_INTERVAL_MS) return;
|
|
442
|
+
lastRejectWarnAt = now;
|
|
443
|
+
console.warn(
|
|
444
|
+
"[neatd] rejecting OTLP spans on /v1/traces \u2014 missing or invalid bearer token (set NEAT_OTEL_TOKEN on the instrumented app)"
|
|
445
|
+
);
|
|
446
|
+
};
|
|
447
|
+
mountBearerAuth(app, {
|
|
448
|
+
token: opts.authToken,
|
|
449
|
+
trustProxy: opts.trustProxy,
|
|
450
|
+
onReject: warnRejectedOtlp
|
|
451
|
+
});
|
|
425
452
|
const queue = [];
|
|
426
453
|
let draining = false;
|
|
427
454
|
let drainPromise = Promise.resolve();
|
|
@@ -590,12 +617,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
|
|
|
590
617
|
}
|
|
591
618
|
}
|
|
592
619
|
}
|
|
593
|
-
var
|
|
620
|
+
var import_node_path42, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
|
|
594
621
|
var init_otel = __esm({
|
|
595
622
|
"src/otel.ts"() {
|
|
596
623
|
"use strict";
|
|
597
624
|
init_cjs_shims();
|
|
598
|
-
|
|
625
|
+
import_node_path42 = __toESM(require("path"), 1);
|
|
599
626
|
import_node_url2 = require("url");
|
|
600
627
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
601
628
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -619,13 +646,13 @@ __export(neatd_exports, {
|
|
|
619
646
|
module.exports = __toCommonJS(neatd_exports);
|
|
620
647
|
init_cjs_shims();
|
|
621
648
|
var import_node_fs27 = require("fs");
|
|
622
|
-
var
|
|
649
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
623
650
|
var import_node_module2 = require("module");
|
|
624
651
|
|
|
625
652
|
// src/daemon.ts
|
|
626
653
|
init_cjs_shims();
|
|
627
654
|
var import_node_fs25 = require("fs");
|
|
628
|
-
var
|
|
655
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
629
656
|
var import_node_module = require("module");
|
|
630
657
|
|
|
631
658
|
// src/graph.ts
|
|
@@ -1155,19 +1182,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1155
1182
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1156
1183
|
let best = { path: [start], edges: [] };
|
|
1157
1184
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1158
|
-
function step(node,
|
|
1159
|
-
if (
|
|
1160
|
-
best = { path: [...
|
|
1185
|
+
function step(node, path47, edges) {
|
|
1186
|
+
if (path47.length > best.path.length) {
|
|
1187
|
+
best = { path: [...path47], edges: [...edges] };
|
|
1161
1188
|
}
|
|
1162
|
-
if (
|
|
1189
|
+
if (path47.length - 1 >= maxDepth) return;
|
|
1163
1190
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1164
1191
|
for (const [srcId, edge] of incoming) {
|
|
1165
1192
|
if (visited.has(srcId)) continue;
|
|
1166
1193
|
visited.add(srcId);
|
|
1167
|
-
|
|
1194
|
+
path47.push(srcId);
|
|
1168
1195
|
edges.push(edge);
|
|
1169
|
-
step(srcId,
|
|
1170
|
-
|
|
1196
|
+
step(srcId, path47, edges);
|
|
1197
|
+
path47.pop();
|
|
1171
1198
|
edges.pop();
|
|
1172
1199
|
visited.delete(srcId);
|
|
1173
1200
|
}
|
|
@@ -1175,14 +1202,14 @@ function longestIncomingWalk(graph, start, maxDepth) {
|
|
|
1175
1202
|
step(start, [start], []);
|
|
1176
1203
|
return best;
|
|
1177
1204
|
}
|
|
1178
|
-
function databaseRootCauseShape(graph, origin,
|
|
1205
|
+
function databaseRootCauseShape(graph, origin, walk3) {
|
|
1179
1206
|
const targetDb = origin;
|
|
1180
1207
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
1181
1208
|
if (candidatePairs.length === 0) return null;
|
|
1182
|
-
for (const id of
|
|
1209
|
+
for (const id of walk3.path) {
|
|
1183
1210
|
const owner = resolveOwningService(graph, id);
|
|
1184
1211
|
if (!owner) continue;
|
|
1185
|
-
const { id:
|
|
1212
|
+
const { id: serviceId4, svc } = owner;
|
|
1186
1213
|
const deps = svc.dependencies ?? {};
|
|
1187
1214
|
for (const pair of candidatePairs) {
|
|
1188
1215
|
const declared = deps[pair.driver];
|
|
@@ -1195,7 +1222,7 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1195
1222
|
);
|
|
1196
1223
|
if (!result.compatible) {
|
|
1197
1224
|
return {
|
|
1198
|
-
rootCauseNode:
|
|
1225
|
+
rootCauseNode: serviceId4,
|
|
1199
1226
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1200
1227
|
...result.minDriverVersion ? {
|
|
1201
1228
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1206,11 +1233,11 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1206
1233
|
}
|
|
1207
1234
|
return null;
|
|
1208
1235
|
}
|
|
1209
|
-
function serviceRootCauseShape(graph, _origin,
|
|
1210
|
-
for (const id of
|
|
1236
|
+
function serviceRootCauseShape(graph, _origin, walk3) {
|
|
1237
|
+
for (const id of walk3.path) {
|
|
1211
1238
|
const owner = resolveOwningService(graph, id);
|
|
1212
1239
|
if (!owner) continue;
|
|
1213
|
-
const { id:
|
|
1240
|
+
const { id: serviceId4, svc } = owner;
|
|
1214
1241
|
const deps = svc.dependencies ?? {};
|
|
1215
1242
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1216
1243
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1219,7 +1246,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1219
1246
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1220
1247
|
if (!result.compatible && result.reason) {
|
|
1221
1248
|
return {
|
|
1222
|
-
rootCauseNode:
|
|
1249
|
+
rootCauseNode: serviceId4,
|
|
1223
1250
|
rootCauseReason: result.reason,
|
|
1224
1251
|
...result.requiredNodeVersion ? {
|
|
1225
1252
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1234,7 +1261,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1234
1261
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1235
1262
|
if (!result.compatible && result.reason) {
|
|
1236
1263
|
return {
|
|
1237
|
-
rootCauseNode:
|
|
1264
|
+
rootCauseNode: serviceId4,
|
|
1238
1265
|
rootCauseReason: result.reason,
|
|
1239
1266
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1240
1267
|
};
|
|
@@ -1243,10 +1270,10 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1243
1270
|
}
|
|
1244
1271
|
return null;
|
|
1245
1272
|
}
|
|
1246
|
-
function fileRootCauseShape(graph, origin,
|
|
1273
|
+
function fileRootCauseShape(graph, origin, walk3) {
|
|
1247
1274
|
const owner = resolveOwningService(graph, origin.id);
|
|
1248
1275
|
if (!owner) return null;
|
|
1249
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
1276
|
+
return serviceRootCauseShape(graph, owner.svc, walk3);
|
|
1250
1277
|
}
|
|
1251
1278
|
var rootCauseShapes = {
|
|
1252
1279
|
[import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
@@ -1258,16 +1285,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
1258
1285
|
const origin = graph.getNodeAttributes(errorNodeId);
|
|
1259
1286
|
const shape = rootCauseShapes[origin.type];
|
|
1260
1287
|
if (shape) {
|
|
1261
|
-
const
|
|
1262
|
-
const match = shape(graph, origin,
|
|
1288
|
+
const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
|
|
1289
|
+
const match = shape(graph, origin, walk3);
|
|
1263
1290
|
if (match) {
|
|
1264
1291
|
const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
|
|
1265
1292
|
return import_types.RootCauseResultSchema.parse({
|
|
1266
1293
|
rootCauseNode: match.rootCauseNode,
|
|
1267
1294
|
rootCauseReason: reason,
|
|
1268
|
-
traversalPath:
|
|
1269
|
-
edgeProvenances:
|
|
1270
|
-
confidence: confidenceFromMix(
|
|
1295
|
+
traversalPath: walk3.path,
|
|
1296
|
+
edgeProvenances: walk3.edges.map((e) => e.provenance),
|
|
1297
|
+
confidence: confidenceFromMix(walk3.edges),
|
|
1271
1298
|
fixRecommendation: match.fixRecommendation
|
|
1272
1299
|
});
|
|
1273
1300
|
}
|
|
@@ -1325,9 +1352,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
1325
1352
|
function isFailingCallEdge(e) {
|
|
1326
1353
|
return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
1327
1354
|
}
|
|
1328
|
-
function callSourcesForService(graph,
|
|
1329
|
-
const ids = [
|
|
1330
|
-
for (const edgeId of graph.outboundEdges(
|
|
1355
|
+
function callSourcesForService(graph, serviceId4) {
|
|
1356
|
+
const ids = [serviceId4];
|
|
1357
|
+
for (const edgeId of graph.outboundEdges(serviceId4)) {
|
|
1331
1358
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1332
1359
|
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
1333
1360
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -1344,9 +1371,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
1344
1371
|
}
|
|
1345
1372
|
return id < curId;
|
|
1346
1373
|
}
|
|
1347
|
-
function dominantFailingCall(graph,
|
|
1374
|
+
function dominantFailingCall(graph, serviceId4, visited) {
|
|
1348
1375
|
let best = null;
|
|
1349
|
-
for (const src of callSourcesForService(graph,
|
|
1376
|
+
for (const src of callSourcesForService(graph, serviceId4)) {
|
|
1350
1377
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
1351
1378
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1352
1379
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -1361,26 +1388,26 @@ function dominantFailingCall(graph, serviceId3, visited) {
|
|
|
1361
1388
|
return best;
|
|
1362
1389
|
}
|
|
1363
1390
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1364
|
-
const
|
|
1391
|
+
const path47 = [originServiceId];
|
|
1365
1392
|
const edges = [];
|
|
1366
1393
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1367
1394
|
let current = originServiceId;
|
|
1368
1395
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1369
1396
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1370
1397
|
if (!hop) break;
|
|
1371
|
-
|
|
1398
|
+
path47.push(hop.nextService);
|
|
1372
1399
|
edges.push(hop.edge);
|
|
1373
1400
|
visited.add(hop.nextService);
|
|
1374
1401
|
current = hop.nextService;
|
|
1375
1402
|
}
|
|
1376
1403
|
if (edges.length === 0) return null;
|
|
1377
|
-
return { path:
|
|
1404
|
+
return { path: path47, edges, culprit: current };
|
|
1378
1405
|
}
|
|
1379
1406
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1380
1407
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1381
1408
|
if (!chain) return null;
|
|
1382
1409
|
const culprit = chain.culprit;
|
|
1383
|
-
const
|
|
1410
|
+
const path47 = [...chain.path];
|
|
1384
1411
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1385
1412
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1386
1413
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1388,14 +1415,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1388
1415
|
if (loc) {
|
|
1389
1416
|
let rootCauseNode = culprit;
|
|
1390
1417
|
if (loc.fileNode) {
|
|
1391
|
-
|
|
1418
|
+
path47.push(loc.fileNode);
|
|
1392
1419
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1393
1420
|
rootCauseNode = loc.fileNode;
|
|
1394
1421
|
}
|
|
1395
1422
|
return import_types.RootCauseResultSchema.parse({
|
|
1396
1423
|
rootCauseNode,
|
|
1397
1424
|
rootCauseReason: loc.rootCauseReason,
|
|
1398
|
-
traversalPath:
|
|
1425
|
+
traversalPath: path47,
|
|
1399
1426
|
edgeProvenances,
|
|
1400
1427
|
confidence,
|
|
1401
1428
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1407,7 +1434,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1407
1434
|
return import_types.RootCauseResultSchema.parse({
|
|
1408
1435
|
rootCauseNode: culprit,
|
|
1409
1436
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1410
|
-
traversalPath:
|
|
1437
|
+
traversalPath: path47,
|
|
1411
1438
|
edgeProvenances,
|
|
1412
1439
|
confidence,
|
|
1413
1440
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -2007,6 +2034,28 @@ var DEFAULT_STALE_THRESHOLDS = {
|
|
|
2007
2034
|
CONFIGURED_BY: DAY_MS,
|
|
2008
2035
|
RUNS_ON: DAY_MS
|
|
2009
2036
|
};
|
|
2037
|
+
var FALLBACK_STALE_THRESHOLD_MS = DAY_MS;
|
|
2038
|
+
function loadStaleThresholdsFromEnv() {
|
|
2039
|
+
const raw = process.env.NEAT_STALE_THRESHOLDS;
|
|
2040
|
+
if (!raw) return DEFAULT_STALE_THRESHOLDS;
|
|
2041
|
+
try {
|
|
2042
|
+
const overrides = JSON.parse(raw);
|
|
2043
|
+
const merged = { ...DEFAULT_STALE_THRESHOLDS };
|
|
2044
|
+
for (const [k, v] of Object.entries(overrides)) {
|
|
2045
|
+
if (typeof v === "number" && Number.isFinite(v) && v >= 0) merged[k] = v;
|
|
2046
|
+
}
|
|
2047
|
+
return merged;
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
console.warn(
|
|
2050
|
+
`[neat] NEAT_STALE_THRESHOLDS could not be parsed (${err.message}); using defaults`
|
|
2051
|
+
);
|
|
2052
|
+
return DEFAULT_STALE_THRESHOLDS;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
function thresholdForEdgeType(edgeType, overrides) {
|
|
2056
|
+
const map = overrides ?? loadStaleThresholdsFromEnv();
|
|
2057
|
+
return map[edgeType] ?? FALLBACK_STALE_THRESHOLD_MS;
|
|
2058
|
+
}
|
|
2010
2059
|
var DEFAULT_INCIDENT_THRESHOLDS = {
|
|
2011
2060
|
threshold: 5,
|
|
2012
2061
|
windowMs: 6e4
|
|
@@ -2309,10 +2358,48 @@ var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
|
2309
2358
|
]);
|
|
2310
2359
|
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
2311
2360
|
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
2361
|
+
var WIRE_SPAN_KIND_CONSUMER = 5;
|
|
2312
2362
|
function spanMintsObservedEdge(kind) {
|
|
2313
2363
|
if (kind === void 0 || kind === 0) return true;
|
|
2314
2364
|
return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
|
|
2315
2365
|
}
|
|
2366
|
+
function spanMintsMessagingEdge(kind) {
|
|
2367
|
+
return kind === WIRE_SPAN_KIND_PRODUCER || kind === WIRE_SPAN_KIND_CONSUMER;
|
|
2368
|
+
}
|
|
2369
|
+
function spanServesGraphqlOperation(kind) {
|
|
2370
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2371
|
+
}
|
|
2372
|
+
function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
|
|
2373
|
+
const id = (0, import_types3.graphqlOperationId)(serviceName, operationType, operationName);
|
|
2374
|
+
if (graph.hasNode(id)) return id;
|
|
2375
|
+
const node = {
|
|
2376
|
+
id,
|
|
2377
|
+
type: import_types3.NodeType.GraphQLOperationNode,
|
|
2378
|
+
name: operationName,
|
|
2379
|
+
service: serviceName,
|
|
2380
|
+
operationType: operationType.toLowerCase(),
|
|
2381
|
+
operationName,
|
|
2382
|
+
discoveredVia: "otel"
|
|
2383
|
+
};
|
|
2384
|
+
graph.addNode(id, node);
|
|
2385
|
+
return id;
|
|
2386
|
+
}
|
|
2387
|
+
function messagingDestinationKind(system) {
|
|
2388
|
+
return `${system}-topic`;
|
|
2389
|
+
}
|
|
2390
|
+
function ensureMessagingDestinationNode(graph, system, destination) {
|
|
2391
|
+
const id = (0, import_types3.infraId)(messagingDestinationKind(system), destination);
|
|
2392
|
+
if (graph.hasNode(id)) return id;
|
|
2393
|
+
const node = {
|
|
2394
|
+
id,
|
|
2395
|
+
type: import_types3.NodeType.InfraNode,
|
|
2396
|
+
name: destination,
|
|
2397
|
+
provider: "self",
|
|
2398
|
+
kind: messagingDestinationKind(system)
|
|
2399
|
+
};
|
|
2400
|
+
graph.addNode(id, node);
|
|
2401
|
+
return id;
|
|
2402
|
+
}
|
|
2316
2403
|
var PARENT_SPAN_CACHE_SIZE = 1e4;
|
|
2317
2404
|
var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2318
2405
|
var parentSpanCache = /* @__PURE__ */ new Map();
|
|
@@ -2406,6 +2493,21 @@ function ensureDatabaseNode(graph, host, engine) {
|
|
|
2406
2493
|
graph.addNode(id, node);
|
|
2407
2494
|
return id;
|
|
2408
2495
|
}
|
|
2496
|
+
function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
|
|
2497
|
+
const id = (0, import_types3.localDatabaseId)(serviceName, name);
|
|
2498
|
+
if (graph.hasNode(id)) return id;
|
|
2499
|
+
const node = {
|
|
2500
|
+
id,
|
|
2501
|
+
type: import_types3.NodeType.DatabaseNode,
|
|
2502
|
+
name,
|
|
2503
|
+
engine,
|
|
2504
|
+
engineVersion: "unknown",
|
|
2505
|
+
compatibleDrivers: [],
|
|
2506
|
+
discoveredVia: "otel"
|
|
2507
|
+
};
|
|
2508
|
+
graph.addNode(id, node);
|
|
2509
|
+
return id;
|
|
2510
|
+
}
|
|
2409
2511
|
function ensureFrontierNode(graph, host, ts) {
|
|
2410
2512
|
const id = frontierIdFor(host);
|
|
2411
2513
|
if (graph.hasNode(id)) {
|
|
@@ -2657,10 +2759,21 @@ async function handleSpan(ctx, span) {
|
|
|
2657
2759
|
let affectedNode = sourceId;
|
|
2658
2760
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2659
2761
|
if (span.dbSystem) {
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2762
|
+
if (mintsFromCallerSide) {
|
|
2763
|
+
const host = pickAddress(span);
|
|
2764
|
+
let targetId;
|
|
2765
|
+
if (host) {
|
|
2766
|
+
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
2767
|
+
targetId = (0, import_types3.databaseId)(host);
|
|
2768
|
+
} else {
|
|
2769
|
+
const localName = span.dbName ?? span.dbSystem;
|
|
2770
|
+
targetId = ensureLocalDatabaseNode(
|
|
2771
|
+
ctx.graph,
|
|
2772
|
+
span.service,
|
|
2773
|
+
localName,
|
|
2774
|
+
span.dbSystem
|
|
2775
|
+
);
|
|
2776
|
+
}
|
|
2664
2777
|
const result = upsertObservedEdge(
|
|
2665
2778
|
ctx.graph,
|
|
2666
2779
|
import_types3.EdgeType.CONNECTS_TO,
|
|
@@ -2672,6 +2785,40 @@ async function handleSpan(ctx, span) {
|
|
|
2672
2785
|
);
|
|
2673
2786
|
if (result) affectedNode = targetId;
|
|
2674
2787
|
}
|
|
2788
|
+
} else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
|
|
2789
|
+
const targetId = ensureMessagingDestinationNode(
|
|
2790
|
+
ctx.graph,
|
|
2791
|
+
span.messagingSystem,
|
|
2792
|
+
span.messagingDestination
|
|
2793
|
+
);
|
|
2794
|
+
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types3.EdgeType.CONSUMES_FROM : import_types3.EdgeType.PUBLISHES_TO;
|
|
2795
|
+
const result = upsertObservedEdge(
|
|
2796
|
+
ctx.graph,
|
|
2797
|
+
edgeType,
|
|
2798
|
+
observedSource(),
|
|
2799
|
+
targetId,
|
|
2800
|
+
ts,
|
|
2801
|
+
isError,
|
|
2802
|
+
callSiteEvidence
|
|
2803
|
+
);
|
|
2804
|
+
if (result) affectedNode = targetId;
|
|
2805
|
+
} else if (span.graphqlOperationName && span.graphqlOperationType && spanServesGraphqlOperation(span.kind)) {
|
|
2806
|
+
const targetId = ensureGraphqlOperationNode(
|
|
2807
|
+
ctx.graph,
|
|
2808
|
+
span.service,
|
|
2809
|
+
span.graphqlOperationType,
|
|
2810
|
+
span.graphqlOperationName
|
|
2811
|
+
);
|
|
2812
|
+
const result = upsertObservedEdge(
|
|
2813
|
+
ctx.graph,
|
|
2814
|
+
import_types3.EdgeType.CONTAINS,
|
|
2815
|
+
observedSource(),
|
|
2816
|
+
targetId,
|
|
2817
|
+
ts,
|
|
2818
|
+
isError,
|
|
2819
|
+
callSiteEvidence
|
|
2820
|
+
);
|
|
2821
|
+
if (result) affectedNode = targetId;
|
|
2675
2822
|
} else {
|
|
2676
2823
|
const host = pickAddress(span);
|
|
2677
2824
|
let resolvedViaAddress = false;
|
|
@@ -2781,29 +2928,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
2781
2928
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
2782
2929
|
});
|
|
2783
2930
|
let promoted = 0;
|
|
2784
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
2931
|
+
for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
|
|
2785
2932
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
2786
2933
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
2787
2934
|
if (!gate.allowed) {
|
|
2788
2935
|
continue;
|
|
2789
2936
|
}
|
|
2790
2937
|
}
|
|
2791
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
2938
|
+
rewireFrontierEdges(graph, frontierId2, serviceId4);
|
|
2792
2939
|
graph.dropNode(frontierId2);
|
|
2793
2940
|
promoted++;
|
|
2794
2941
|
}
|
|
2795
2942
|
return promoted;
|
|
2796
2943
|
}
|
|
2797
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
2944
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId4) {
|
|
2798
2945
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
2799
2946
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
2800
2947
|
for (const edgeId of inbound) {
|
|
2801
2948
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
2802
|
-
rebuildEdge(graph, edge, edge.source,
|
|
2949
|
+
rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
|
|
2803
2950
|
}
|
|
2804
2951
|
for (const edgeId of outbound) {
|
|
2805
2952
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
2806
|
-
rebuildEdge(graph, edge,
|
|
2953
|
+
rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
|
|
2807
2954
|
}
|
|
2808
2955
|
}
|
|
2809
2956
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -2832,6 +2979,51 @@ function pickLater(a, b) {
|
|
|
2832
2979
|
if (!b) return a;
|
|
2833
2980
|
return new Date(a).getTime() >= new Date(b).getTime() ? a : b;
|
|
2834
2981
|
}
|
|
2982
|
+
async function markStaleEdges(graph, options = {}) {
|
|
2983
|
+
const thresholds = options.thresholds ?? loadStaleThresholdsFromEnv();
|
|
2984
|
+
const now = options.now ?? Date.now();
|
|
2985
|
+
const events = [];
|
|
2986
|
+
const project = options.project ?? DEFAULT_PROJECT;
|
|
2987
|
+
graph.forEachEdge((id, attrs) => {
|
|
2988
|
+
const e = attrs;
|
|
2989
|
+
if (e.provenance !== import_types3.Provenance.OBSERVED) return;
|
|
2990
|
+
if (!e.lastObserved) return;
|
|
2991
|
+
const threshold = thresholdForEdgeType(e.type, thresholds);
|
|
2992
|
+
const age = now - new Date(e.lastObserved).getTime();
|
|
2993
|
+
if (age > threshold) {
|
|
2994
|
+
const updated = { ...e, provenance: import_types3.Provenance.STALE, confidence: 0.3 };
|
|
2995
|
+
graph.replaceEdgeAttributes(id, updated);
|
|
2996
|
+
events.push({
|
|
2997
|
+
edgeId: id,
|
|
2998
|
+
source: e.source,
|
|
2999
|
+
target: e.target,
|
|
3000
|
+
edgeType: e.type,
|
|
3001
|
+
thresholdMs: threshold,
|
|
3002
|
+
ageMs: age,
|
|
3003
|
+
lastObserved: e.lastObserved,
|
|
3004
|
+
transitionedAt: new Date(now).toISOString()
|
|
3005
|
+
});
|
|
3006
|
+
emitNeatEvent({
|
|
3007
|
+
type: "stale-transition",
|
|
3008
|
+
project,
|
|
3009
|
+
payload: {
|
|
3010
|
+
edgeId: id,
|
|
3011
|
+
from: import_types3.Provenance.OBSERVED,
|
|
3012
|
+
to: import_types3.Provenance.STALE
|
|
3013
|
+
}
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
});
|
|
3017
|
+
if (options.staleEventsPath && events.length > 0) {
|
|
3018
|
+
await appendStaleEvents(options.staleEventsPath, events);
|
|
3019
|
+
}
|
|
3020
|
+
return { count: events.length, events };
|
|
3021
|
+
}
|
|
3022
|
+
async function appendStaleEvents(staleEventsPath, events) {
|
|
3023
|
+
await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(staleEventsPath), { recursive: true });
|
|
3024
|
+
const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
3025
|
+
await import_node_fs3.promises.appendFile(staleEventsPath, lines, "utf8");
|
|
3026
|
+
}
|
|
2835
3027
|
async function readStaleEvents(staleEventsPath) {
|
|
2836
3028
|
try {
|
|
2837
3029
|
const raw = await import_node_fs3.promises.readFile(staleEventsPath, "utf8");
|
|
@@ -2841,6 +3033,31 @@ async function readStaleEvents(staleEventsPath) {
|
|
|
2841
3033
|
throw err;
|
|
2842
3034
|
}
|
|
2843
3035
|
}
|
|
3036
|
+
function startStalenessLoop(graph, options = {}) {
|
|
3037
|
+
let stopped = false;
|
|
3038
|
+
const intervalMs = options.intervalMs ?? 6e4;
|
|
3039
|
+
const tick = () => {
|
|
3040
|
+
if (stopped) return;
|
|
3041
|
+
void (async () => {
|
|
3042
|
+
try {
|
|
3043
|
+
await markStaleEdges(graph, {
|
|
3044
|
+
thresholds: options.thresholds,
|
|
3045
|
+
staleEventsPath: options.staleEventsPath,
|
|
3046
|
+
project: options.project
|
|
3047
|
+
});
|
|
3048
|
+
if (options.onPolicyTrigger) await options.onPolicyTrigger(graph);
|
|
3049
|
+
} catch (err) {
|
|
3050
|
+
console.error("staleness tick failed", err);
|
|
3051
|
+
}
|
|
3052
|
+
})();
|
|
3053
|
+
};
|
|
3054
|
+
const interval = setInterval(tick, intervalMs);
|
|
3055
|
+
if (typeof interval.unref === "function") interval.unref();
|
|
3056
|
+
return () => {
|
|
3057
|
+
stopped = true;
|
|
3058
|
+
clearInterval(interval);
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
2844
3061
|
async function readErrorEvents(errorsPath) {
|
|
2845
3062
|
try {
|
|
2846
3063
|
const raw = await import_node_fs3.promises.readFile(errorsPath, "utf8");
|
|
@@ -3492,9 +3709,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
3492
3709
|
"StatefulSet",
|
|
3493
3710
|
"DaemonSet"
|
|
3494
3711
|
]);
|
|
3495
|
-
function addAliases(graph,
|
|
3496
|
-
if (!graph.hasNode(
|
|
3497
|
-
const node = graph.getNodeAttributes(
|
|
3712
|
+
function addAliases(graph, serviceId4, candidates) {
|
|
3713
|
+
if (!graph.hasNode(serviceId4)) return;
|
|
3714
|
+
const node = graph.getNodeAttributes(serviceId4);
|
|
3498
3715
|
if (node.type !== import_types6.NodeType.ServiceNode) return;
|
|
3499
3716
|
const set = new Set(node.aliases ?? []);
|
|
3500
3717
|
for (const c of candidates) {
|
|
@@ -3504,7 +3721,7 @@ function addAliases(graph, serviceId3, candidates) {
|
|
|
3504
3721
|
}
|
|
3505
3722
|
if (set.size === 0) return;
|
|
3506
3723
|
const updated = { ...node, aliases: [...set].sort() };
|
|
3507
|
-
graph.replaceNodeAttributes(
|
|
3724
|
+
graph.replaceNodeAttributes(serviceId4, updated);
|
|
3508
3725
|
}
|
|
3509
3726
|
function indexServicesByName(services) {
|
|
3510
3727
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -3537,12 +3754,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
3537
3754
|
}
|
|
3538
3755
|
if (!compose?.services) return;
|
|
3539
3756
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3540
|
-
const
|
|
3541
|
-
if (!
|
|
3757
|
+
const serviceId4 = serviceIndex.get(composeName);
|
|
3758
|
+
if (!serviceId4) continue;
|
|
3542
3759
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
3543
3760
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
3544
3761
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
3545
|
-
addAliases(graph,
|
|
3762
|
+
addAliases(graph, serviceId4, aliases);
|
|
3546
3763
|
}
|
|
3547
3764
|
}
|
|
3548
3765
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -3655,16 +3872,28 @@ init_cjs_shims();
|
|
|
3655
3872
|
var import_node_fs10 = require("fs");
|
|
3656
3873
|
var import_node_path10 = __toESM(require("path"), 1);
|
|
3657
3874
|
var import_types7 = require("@neat.is/types");
|
|
3875
|
+
function buildServiceHostIndex(services) {
|
|
3876
|
+
const knownHosts = /* @__PURE__ */ new Set();
|
|
3877
|
+
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3878
|
+
for (const service of services) {
|
|
3879
|
+
const base = import_node_path10.default.basename(service.dir);
|
|
3880
|
+
knownHosts.add(base);
|
|
3881
|
+
knownHosts.add(service.pkg.name);
|
|
3882
|
+
hostToNodeId.set(base, service.node.id);
|
|
3883
|
+
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3884
|
+
}
|
|
3885
|
+
return { knownHosts, hostToNodeId };
|
|
3886
|
+
}
|
|
3658
3887
|
async function walkSourceFiles(dir) {
|
|
3659
3888
|
const out = [];
|
|
3660
|
-
async function
|
|
3889
|
+
async function walk3(current) {
|
|
3661
3890
|
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3662
3891
|
for (const entry2 of entries) {
|
|
3663
3892
|
const full = import_node_path10.default.join(current, entry2.name);
|
|
3664
3893
|
if (entry2.isDirectory()) {
|
|
3665
3894
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3666
3895
|
if (await isPythonVenvDir(full)) continue;
|
|
3667
|
-
await
|
|
3896
|
+
await walk3(full);
|
|
3668
3897
|
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
|
|
3669
3898
|
// would attribute our instrumentation imports to the user's service.
|
|
3670
3899
|
!isNeatAuthoredSourceFile(entry2.name)) {
|
|
@@ -3672,7 +3901,7 @@ async function walkSourceFiles(dir) {
|
|
|
3672
3901
|
}
|
|
3673
3902
|
}
|
|
3674
3903
|
}
|
|
3675
|
-
await
|
|
3904
|
+
await walk3(dir);
|
|
3676
3905
|
return out;
|
|
3677
3906
|
}
|
|
3678
3907
|
async function loadSourceFiles(dir) {
|
|
@@ -4766,20 +4995,20 @@ var import_node_path21 = __toESM(require("path"), 1);
|
|
|
4766
4995
|
var import_types10 = require("@neat.is/types");
|
|
4767
4996
|
async function walkConfigFiles(dir) {
|
|
4768
4997
|
const out = [];
|
|
4769
|
-
async function
|
|
4998
|
+
async function walk3(current) {
|
|
4770
4999
|
const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
|
|
4771
5000
|
for (const entry2 of entries) {
|
|
4772
5001
|
const full = import_node_path21.default.join(current, entry2.name);
|
|
4773
5002
|
if (entry2.isDirectory()) {
|
|
4774
5003
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
4775
5004
|
if (await isPythonVenvDir(full)) continue;
|
|
4776
|
-
await
|
|
5005
|
+
await walk3(full);
|
|
4777
5006
|
} else if (entry2.isFile() && isConfigFile(entry2.name).match) {
|
|
4778
5007
|
out.push(full);
|
|
4779
5008
|
}
|
|
4780
5009
|
}
|
|
4781
5010
|
}
|
|
4782
|
-
await
|
|
5011
|
+
await walk3(dir);
|
|
4783
5012
|
return out;
|
|
4784
5013
|
}
|
|
4785
5014
|
async function addConfigNodes(graph, services, scanPath) {
|
|
@@ -4827,17 +5056,347 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
4827
5056
|
return { nodesAdded, edgesAdded };
|
|
4828
5057
|
}
|
|
4829
5058
|
|
|
5059
|
+
// src/extract/routes.ts
|
|
5060
|
+
init_cjs_shims();
|
|
5061
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
5062
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
5063
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5064
|
+
var import_types11 = require("@neat.is/types");
|
|
5065
|
+
var PARSE_CHUNK2 = 16384;
|
|
5066
|
+
function parseSource2(parser, source) {
|
|
5067
|
+
return parser.parse(
|
|
5068
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
5069
|
+
);
|
|
5070
|
+
}
|
|
5071
|
+
function makeJsParser2() {
|
|
5072
|
+
const p = new import_tree_sitter2.default();
|
|
5073
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
5074
|
+
return p;
|
|
5075
|
+
}
|
|
5076
|
+
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
5077
|
+
"get",
|
|
5078
|
+
"post",
|
|
5079
|
+
"put",
|
|
5080
|
+
"patch",
|
|
5081
|
+
"delete",
|
|
5082
|
+
"options",
|
|
5083
|
+
"head",
|
|
5084
|
+
"all"
|
|
5085
|
+
]);
|
|
5086
|
+
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
5087
|
+
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5088
|
+
function canonicalizeTemplate(raw) {
|
|
5089
|
+
let p = raw.split("?")[0].split("#")[0];
|
|
5090
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
5091
|
+
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
5092
|
+
return p;
|
|
5093
|
+
}
|
|
5094
|
+
function isDynamicSegment(seg) {
|
|
5095
|
+
if (seg.length === 0) return false;
|
|
5096
|
+
if (seg.includes(":")) return true;
|
|
5097
|
+
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
5098
|
+
if (/^\d+$/.test(seg)) return true;
|
|
5099
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg)) return true;
|
|
5100
|
+
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
5101
|
+
return false;
|
|
5102
|
+
}
|
|
5103
|
+
function normalizePathTemplate(raw) {
|
|
5104
|
+
const canonical = canonicalizeTemplate(raw);
|
|
5105
|
+
const segments = canonical.split("/").filter((s) => s.length > 0);
|
|
5106
|
+
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
5107
|
+
return "/" + normalised.join("/");
|
|
5108
|
+
}
|
|
5109
|
+
function walk(node, visit) {
|
|
5110
|
+
visit(node);
|
|
5111
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5112
|
+
const child = node.namedChild(i);
|
|
5113
|
+
if (child) walk(child, visit);
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
function staticStringText(node) {
|
|
5117
|
+
if (node.type === "string") {
|
|
5118
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5119
|
+
const child = node.namedChild(i);
|
|
5120
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5121
|
+
}
|
|
5122
|
+
return "";
|
|
5123
|
+
}
|
|
5124
|
+
if (node.type === "template_string") {
|
|
5125
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5126
|
+
if (node.namedChild(i)?.type === "template_substitution") return null;
|
|
5127
|
+
}
|
|
5128
|
+
const raw = node.text;
|
|
5129
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
5130
|
+
}
|
|
5131
|
+
return null;
|
|
5132
|
+
}
|
|
5133
|
+
function objectStringProp(objNode, key) {
|
|
5134
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5135
|
+
const pair = objNode.namedChild(i);
|
|
5136
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5137
|
+
const k = pair.childForFieldName("key");
|
|
5138
|
+
if (!k) continue;
|
|
5139
|
+
const kText = k.type === "string" ? staticStringText(k) : k.text;
|
|
5140
|
+
if (kText !== key) continue;
|
|
5141
|
+
const v = pair.childForFieldName("value");
|
|
5142
|
+
if (v) return staticStringText(v);
|
|
5143
|
+
}
|
|
5144
|
+
return null;
|
|
5145
|
+
}
|
|
5146
|
+
function fastifyRouteMethods(objNode) {
|
|
5147
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5148
|
+
const pair = objNode.namedChild(i);
|
|
5149
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5150
|
+
const k = pair.childForFieldName("key");
|
|
5151
|
+
const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
|
|
5152
|
+
if (kText !== "method") continue;
|
|
5153
|
+
const v = pair.childForFieldName("value");
|
|
5154
|
+
if (!v) return [];
|
|
5155
|
+
if (v.type === "string" || v.type === "template_string") {
|
|
5156
|
+
const s = staticStringText(v);
|
|
5157
|
+
return s ? [s.toUpperCase()] : [];
|
|
5158
|
+
}
|
|
5159
|
+
if (v.type === "array") {
|
|
5160
|
+
const out = [];
|
|
5161
|
+
for (let j = 0; j < v.namedChildCount; j++) {
|
|
5162
|
+
const el = v.namedChild(j);
|
|
5163
|
+
if (el && (el.type === "string" || el.type === "template_string")) {
|
|
5164
|
+
const s = staticStringText(el);
|
|
5165
|
+
if (s) out.push(s.toUpperCase());
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
return out;
|
|
5169
|
+
}
|
|
5170
|
+
}
|
|
5171
|
+
return [];
|
|
5172
|
+
}
|
|
5173
|
+
function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
|
|
5174
|
+
const tree = parseSource2(parser, source);
|
|
5175
|
+
const out = [];
|
|
5176
|
+
const framework = hasExpress ? "express" : "fastify";
|
|
5177
|
+
walk(tree.rootNode, (node) => {
|
|
5178
|
+
if (node.type !== "call_expression") return;
|
|
5179
|
+
const fn = node.childForFieldName("function");
|
|
5180
|
+
if (!fn || fn.type !== "member_expression") return;
|
|
5181
|
+
const prop = fn.childForFieldName("property");
|
|
5182
|
+
if (!prop) return;
|
|
5183
|
+
const method = prop.text.toLowerCase();
|
|
5184
|
+
const args = node.childForFieldName("arguments");
|
|
5185
|
+
const first = args?.namedChild(0);
|
|
5186
|
+
if (!first) return;
|
|
5187
|
+
const line = node.startPosition.row + 1;
|
|
5188
|
+
if (ROUTER_METHODS.has(method)) {
|
|
5189
|
+
const p = staticStringText(first);
|
|
5190
|
+
if (p && p.startsWith("/")) {
|
|
5191
|
+
out.push({
|
|
5192
|
+
method: method === "all" ? "ALL" : method.toUpperCase(),
|
|
5193
|
+
pathTemplate: canonicalizeTemplate(p),
|
|
5194
|
+
line,
|
|
5195
|
+
framework
|
|
5196
|
+
});
|
|
5197
|
+
}
|
|
5198
|
+
return;
|
|
5199
|
+
}
|
|
5200
|
+
if (method === "route" && hasFastify && first.type === "object") {
|
|
5201
|
+
const url = objectStringProp(first, "url");
|
|
5202
|
+
if (!url || !url.startsWith("/")) return;
|
|
5203
|
+
const methods = fastifyRouteMethods(first);
|
|
5204
|
+
const list = methods.length > 0 ? methods : ["ALL"];
|
|
5205
|
+
for (const m of list) {
|
|
5206
|
+
out.push({
|
|
5207
|
+
method: m === "ALL" ? "ALL" : m.toUpperCase(),
|
|
5208
|
+
pathTemplate: canonicalizeTemplate(url),
|
|
5209
|
+
line,
|
|
5210
|
+
framework: "fastify"
|
|
5211
|
+
});
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
});
|
|
5215
|
+
return out;
|
|
5216
|
+
}
|
|
5217
|
+
function segmentsOf(relFile) {
|
|
5218
|
+
return toPosix2(relFile).split("/").filter((s) => s.length > 0);
|
|
5219
|
+
}
|
|
5220
|
+
function isNextAppRouteFile(relFile) {
|
|
5221
|
+
const segs = segmentsOf(relFile);
|
|
5222
|
+
if (!segs.includes("app")) return false;
|
|
5223
|
+
const base = segs[segs.length - 1] ?? "";
|
|
5224
|
+
return /^route\.(?:js|jsx|mjs|cjs|ts|tsx)$/.test(base);
|
|
5225
|
+
}
|
|
5226
|
+
function isNextPagesApiFile(relFile) {
|
|
5227
|
+
const segs = segmentsOf(relFile);
|
|
5228
|
+
const pagesIdx = segs.indexOf("pages");
|
|
5229
|
+
if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
|
|
5230
|
+
const base = segs[segs.length - 1] ?? "";
|
|
5231
|
+
if (/^_(app|document|middleware)\./.test(base)) return false;
|
|
5232
|
+
return JS_ROUTE_EXTENSIONS.has(import_node_path22.default.extname(base));
|
|
5233
|
+
}
|
|
5234
|
+
function nextSegment(seg) {
|
|
5235
|
+
if (seg.startsWith("(") && seg.endsWith(")")) return null;
|
|
5236
|
+
const catchAll = seg.match(/^\[\[?\.\.\.(.+?)\]?\]$/);
|
|
5237
|
+
if (catchAll) return ":" + catchAll[1];
|
|
5238
|
+
const dynamic = seg.match(/^\[(.+?)\]$/);
|
|
5239
|
+
if (dynamic) return ":" + dynamic[1];
|
|
5240
|
+
return seg;
|
|
5241
|
+
}
|
|
5242
|
+
function nextAppPathTemplate(relFile) {
|
|
5243
|
+
const segs = segmentsOf(relFile);
|
|
5244
|
+
const appIdx = segs.lastIndexOf("app");
|
|
5245
|
+
const between = segs.slice(appIdx + 1, segs.length - 1);
|
|
5246
|
+
const parts = [];
|
|
5247
|
+
for (const seg of between) {
|
|
5248
|
+
const mapped = nextSegment(seg);
|
|
5249
|
+
if (mapped !== null) parts.push(mapped);
|
|
5250
|
+
}
|
|
5251
|
+
return "/" + parts.join("/");
|
|
5252
|
+
}
|
|
5253
|
+
function nextPagesApiPathTemplate(relFile) {
|
|
5254
|
+
const segs = segmentsOf(relFile);
|
|
5255
|
+
const pagesIdx = segs.indexOf("pages");
|
|
5256
|
+
const rest = segs.slice(pagesIdx + 1);
|
|
5257
|
+
const parts = [];
|
|
5258
|
+
for (let i = 0; i < rest.length; i++) {
|
|
5259
|
+
let seg = rest[i];
|
|
5260
|
+
if (i === rest.length - 1) {
|
|
5261
|
+
seg = seg.replace(/\.(?:js|jsx|mjs|cjs|ts|tsx)$/, "");
|
|
5262
|
+
if (seg === "index") continue;
|
|
5263
|
+
}
|
|
5264
|
+
const mapped = nextSegment(seg);
|
|
5265
|
+
if (mapped !== null) parts.push(mapped);
|
|
5266
|
+
}
|
|
5267
|
+
return "/" + parts.join("/");
|
|
5268
|
+
}
|
|
5269
|
+
function nextAppMethods(root) {
|
|
5270
|
+
const out = [];
|
|
5271
|
+
walk(root, (node) => {
|
|
5272
|
+
if (node.type !== "export_statement") return;
|
|
5273
|
+
const decl = node.childForFieldName("declaration");
|
|
5274
|
+
if (!decl) return;
|
|
5275
|
+
const line = node.startPosition.row + 1;
|
|
5276
|
+
if (decl.type === "function_declaration") {
|
|
5277
|
+
const name = decl.childForFieldName("name")?.text;
|
|
5278
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
5279
|
+
return;
|
|
5280
|
+
}
|
|
5281
|
+
if (decl.type === "lexical_declaration" || decl.type === "variable_declaration") {
|
|
5282
|
+
for (let i = 0; i < decl.namedChildCount; i++) {
|
|
5283
|
+
const d = decl.namedChild(i);
|
|
5284
|
+
if (d?.type !== "variable_declarator") continue;
|
|
5285
|
+
const name = d.childForFieldName("name")?.text;
|
|
5286
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
});
|
|
5290
|
+
return out;
|
|
5291
|
+
}
|
|
5292
|
+
function nextRoutesFromFile(source, relFile, parser) {
|
|
5293
|
+
if (isNextAppRouteFile(relFile)) {
|
|
5294
|
+
const tree = parseSource2(parser, source);
|
|
5295
|
+
const template = nextAppPathTemplate(relFile);
|
|
5296
|
+
return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
|
|
5297
|
+
method,
|
|
5298
|
+
pathTemplate: canonicalizeTemplate(template),
|
|
5299
|
+
line,
|
|
5300
|
+
framework: "next"
|
|
5301
|
+
}));
|
|
5302
|
+
}
|
|
5303
|
+
if (isNextPagesApiFile(relFile)) {
|
|
5304
|
+
return [
|
|
5305
|
+
{
|
|
5306
|
+
method: "ALL",
|
|
5307
|
+
pathTemplate: canonicalizeTemplate(nextPagesApiPathTemplate(relFile)),
|
|
5308
|
+
line: 1,
|
|
5309
|
+
framework: "next"
|
|
5310
|
+
}
|
|
5311
|
+
];
|
|
5312
|
+
}
|
|
5313
|
+
return [];
|
|
5314
|
+
}
|
|
5315
|
+
async function addRoutes(graph, services) {
|
|
5316
|
+
const jsParser = makeJsParser2();
|
|
5317
|
+
let nodesAdded = 0;
|
|
5318
|
+
let edgesAdded = 0;
|
|
5319
|
+
for (const service of services) {
|
|
5320
|
+
const deps = {
|
|
5321
|
+
...service.pkg.dependencies ?? {},
|
|
5322
|
+
...service.pkg.devDependencies ?? {}
|
|
5323
|
+
};
|
|
5324
|
+
const hasExpress = deps["express"] !== void 0;
|
|
5325
|
+
const hasFastify = deps["fastify"] !== void 0;
|
|
5326
|
+
const hasNext = deps["next"] !== void 0;
|
|
5327
|
+
if (!hasExpress && !hasFastify && !hasNext) continue;
|
|
5328
|
+
const files = await loadSourceFiles(service.dir);
|
|
5329
|
+
for (const file of files) {
|
|
5330
|
+
if (isTestPath(file.path)) continue;
|
|
5331
|
+
if (!JS_ROUTE_EXTENSIONS.has(import_node_path22.default.extname(file.path))) continue;
|
|
5332
|
+
const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
|
|
5333
|
+
let routes;
|
|
5334
|
+
try {
|
|
5335
|
+
if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
5336
|
+
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
5337
|
+
} else if (hasExpress || hasFastify) {
|
|
5338
|
+
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
|
|
5339
|
+
} else {
|
|
5340
|
+
routes = [];
|
|
5341
|
+
}
|
|
5342
|
+
} catch (err) {
|
|
5343
|
+
recordExtractionError("route extraction", file.path, err);
|
|
5344
|
+
continue;
|
|
5345
|
+
}
|
|
5346
|
+
if (routes.length === 0) continue;
|
|
5347
|
+
for (const route of routes) {
|
|
5348
|
+
const rid = (0, import_types11.routeId)(service.pkg.name, route.method, route.pathTemplate);
|
|
5349
|
+
if (!graph.hasNode(rid)) {
|
|
5350
|
+
const node = {
|
|
5351
|
+
id: rid,
|
|
5352
|
+
type: import_types11.NodeType.RouteNode,
|
|
5353
|
+
name: `${route.method} ${route.pathTemplate}`,
|
|
5354
|
+
service: service.pkg.name,
|
|
5355
|
+
method: route.method,
|
|
5356
|
+
pathTemplate: route.pathTemplate,
|
|
5357
|
+
path: relFile,
|
|
5358
|
+
line: route.line,
|
|
5359
|
+
framework: route.framework,
|
|
5360
|
+
discoveredVia: "static"
|
|
5361
|
+
};
|
|
5362
|
+
graph.addNode(rid, node);
|
|
5363
|
+
nodesAdded++;
|
|
5364
|
+
}
|
|
5365
|
+
const containsId = (0, import_types11.extractedEdgeId)(service.node.id, rid, import_types11.EdgeType.CONTAINS);
|
|
5366
|
+
if (!graph.hasEdge(containsId)) {
|
|
5367
|
+
const edge = {
|
|
5368
|
+
id: containsId,
|
|
5369
|
+
source: service.node.id,
|
|
5370
|
+
target: rid,
|
|
5371
|
+
type: import_types11.EdgeType.CONTAINS,
|
|
5372
|
+
provenance: import_types11.Provenance.EXTRACTED,
|
|
5373
|
+
confidence: (0, import_types11.confidenceForExtracted)("structural"),
|
|
5374
|
+
evidence: {
|
|
5375
|
+
file: relFile,
|
|
5376
|
+
line: route.line,
|
|
5377
|
+
snippet: snippet(file.content, route.line)
|
|
5378
|
+
}
|
|
5379
|
+
};
|
|
5380
|
+
graph.addEdgeWithKey(containsId, service.node.id, rid, edge);
|
|
5381
|
+
edgesAdded++;
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
return { nodesAdded, edgesAdded };
|
|
5387
|
+
}
|
|
5388
|
+
|
|
4830
5389
|
// src/extract/calls/index.ts
|
|
4831
5390
|
init_cjs_shims();
|
|
4832
|
-
var
|
|
5391
|
+
var import_types19 = require("@neat.is/types");
|
|
4833
5392
|
|
|
4834
5393
|
// src/extract/calls/http.ts
|
|
4835
5394
|
init_cjs_shims();
|
|
4836
|
-
var
|
|
4837
|
-
var
|
|
4838
|
-
var
|
|
5395
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
5396
|
+
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
5397
|
+
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
4839
5398
|
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
4840
|
-
var
|
|
5399
|
+
var import_types12 = require("@neat.is/types");
|
|
4841
5400
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
4842
5401
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
4843
5402
|
function isInsideJsxExternalLink(node) {
|
|
@@ -4865,14 +5424,14 @@ function collectStringLiterals(node, out) {
|
|
|
4865
5424
|
if (child) collectStringLiterals(child, out);
|
|
4866
5425
|
}
|
|
4867
5426
|
}
|
|
4868
|
-
var
|
|
4869
|
-
function
|
|
5427
|
+
var PARSE_CHUNK3 = 16384;
|
|
5428
|
+
function parseSource3(parser, source) {
|
|
4870
5429
|
return parser.parse(
|
|
4871
|
-
(index) => index >= source.length ? "" : source.slice(index, index +
|
|
5430
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
|
|
4872
5431
|
);
|
|
4873
5432
|
}
|
|
4874
5433
|
function callsFromSource(source, parser, knownHosts) {
|
|
4875
|
-
const tree =
|
|
5434
|
+
const tree = parseSource3(parser, source);
|
|
4876
5435
|
const literals = [];
|
|
4877
5436
|
collectStringLiterals(tree.rootNode, literals);
|
|
4878
5437
|
const out = [];
|
|
@@ -4886,27 +5445,20 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
4886
5445
|
}
|
|
4887
5446
|
return out;
|
|
4888
5447
|
}
|
|
4889
|
-
function
|
|
4890
|
-
const p = new
|
|
4891
|
-
p.setLanguage(
|
|
5448
|
+
function makeJsParser3() {
|
|
5449
|
+
const p = new import_tree_sitter3.default();
|
|
5450
|
+
p.setLanguage(import_tree_sitter_javascript3.default);
|
|
4892
5451
|
return p;
|
|
4893
5452
|
}
|
|
4894
5453
|
function makePyParser2() {
|
|
4895
|
-
const p = new
|
|
5454
|
+
const p = new import_tree_sitter3.default();
|
|
4896
5455
|
p.setLanguage(import_tree_sitter_python2.default);
|
|
4897
5456
|
return p;
|
|
4898
5457
|
}
|
|
4899
5458
|
async function addHttpCallEdges(graph, services) {
|
|
4900
|
-
const jsParser =
|
|
5459
|
+
const jsParser = makeJsParser3();
|
|
4901
5460
|
const pyParser = makePyParser2();
|
|
4902
|
-
const knownHosts
|
|
4903
|
-
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
4904
|
-
for (const service of services) {
|
|
4905
|
-
knownHosts.add(import_node_path22.default.basename(service.dir));
|
|
4906
|
-
knownHosts.add(service.pkg.name);
|
|
4907
|
-
hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
|
|
4908
|
-
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
4909
|
-
}
|
|
5461
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
4910
5462
|
let nodesAdded = 0;
|
|
4911
5463
|
let edgesAdded = 0;
|
|
4912
5464
|
for (const service of services) {
|
|
@@ -4914,7 +5466,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4914
5466
|
const seen = /* @__PURE__ */ new Set();
|
|
4915
5467
|
for (const file of files) {
|
|
4916
5468
|
if (isTestPath(file.path)) continue;
|
|
4917
|
-
const parser =
|
|
5469
|
+
const parser = import_node_path23.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
4918
5470
|
let sites;
|
|
4919
5471
|
try {
|
|
4920
5472
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -4923,14 +5475,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4923
5475
|
continue;
|
|
4924
5476
|
}
|
|
4925
5477
|
if (sites.length === 0) continue;
|
|
4926
|
-
const relFile = toPosix2(
|
|
5478
|
+
const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
|
|
4927
5479
|
for (const site of sites) {
|
|
4928
5480
|
const targetId = hostToNodeId.get(site.host);
|
|
4929
5481
|
if (!targetId || targetId === service.node.id) continue;
|
|
4930
5482
|
const dedupKey = `${relFile}|${targetId}`;
|
|
4931
5483
|
if (seen.has(dedupKey)) continue;
|
|
4932
5484
|
seen.add(dedupKey);
|
|
4933
|
-
const confidence = (0,
|
|
5485
|
+
const confidence = (0, import_types12.confidenceForExtracted)("url-literal-service-target");
|
|
4934
5486
|
const ev = {
|
|
4935
5487
|
file: relFile,
|
|
4936
5488
|
line: site.line,
|
|
@@ -4944,25 +5496,25 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4944
5496
|
);
|
|
4945
5497
|
nodesAdded += n;
|
|
4946
5498
|
edgesAdded += e;
|
|
4947
|
-
if (!(0,
|
|
5499
|
+
if (!(0, import_types12.passesExtractedFloor)(confidence)) {
|
|
4948
5500
|
noteExtractedDropped({
|
|
4949
5501
|
source: fileNodeId,
|
|
4950
5502
|
target: targetId,
|
|
4951
|
-
type:
|
|
5503
|
+
type: import_types12.EdgeType.CALLS,
|
|
4952
5504
|
confidence,
|
|
4953
5505
|
confidenceKind: "url-literal-service-target",
|
|
4954
5506
|
evidence: ev
|
|
4955
5507
|
});
|
|
4956
5508
|
continue;
|
|
4957
5509
|
}
|
|
4958
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId,
|
|
5510
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types12.EdgeType.CALLS);
|
|
4959
5511
|
if (!graph.hasEdge(edgeId)) {
|
|
4960
5512
|
const edge = {
|
|
4961
5513
|
id: edgeId,
|
|
4962
5514
|
source: fileNodeId,
|
|
4963
5515
|
target: targetId,
|
|
4964
|
-
type:
|
|
4965
|
-
provenance:
|
|
5516
|
+
type: import_types12.EdgeType.CALLS,
|
|
5517
|
+
provenance: import_types12.Provenance.EXTRACTED,
|
|
4966
5518
|
confidence,
|
|
4967
5519
|
evidence: ev
|
|
4968
5520
|
};
|
|
@@ -4975,10 +5527,279 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4975
5527
|
return { nodesAdded, edgesAdded };
|
|
4976
5528
|
}
|
|
4977
5529
|
|
|
5530
|
+
// src/extract/calls/route-match.ts
|
|
5531
|
+
init_cjs_shims();
|
|
5532
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
5533
|
+
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
5534
|
+
var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5535
|
+
var import_types13 = require("@neat.is/types");
|
|
5536
|
+
var PARSE_CHUNK4 = 16384;
|
|
5537
|
+
function parseSource4(parser, source) {
|
|
5538
|
+
return parser.parse(
|
|
5539
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK4)
|
|
5540
|
+
);
|
|
5541
|
+
}
|
|
5542
|
+
function makeJsParser4() {
|
|
5543
|
+
const p = new import_tree_sitter4.default();
|
|
5544
|
+
p.setLanguage(import_tree_sitter_javascript4.default);
|
|
5545
|
+
return p;
|
|
5546
|
+
}
|
|
5547
|
+
var JS_CLIENT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5548
|
+
var AXIOS_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "head", "options", "request"]);
|
|
5549
|
+
function walk2(node, visit) {
|
|
5550
|
+
visit(node);
|
|
5551
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5552
|
+
const child = node.namedChild(i);
|
|
5553
|
+
if (child) walk2(child, visit);
|
|
5554
|
+
}
|
|
5555
|
+
}
|
|
5556
|
+
function reconstructUrl(node) {
|
|
5557
|
+
if (node.type === "string") {
|
|
5558
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5559
|
+
const child = node.namedChild(i);
|
|
5560
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5561
|
+
}
|
|
5562
|
+
return "";
|
|
5563
|
+
}
|
|
5564
|
+
if (node.type === "template_string") {
|
|
5565
|
+
let out = "";
|
|
5566
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5567
|
+
const child = node.namedChild(i);
|
|
5568
|
+
if (!child) continue;
|
|
5569
|
+
if (child.type === "string_fragment") out += child.text;
|
|
5570
|
+
else if (child.type === "template_substitution") out += ":param";
|
|
5571
|
+
}
|
|
5572
|
+
if (out.length === 0) {
|
|
5573
|
+
const raw = node.text;
|
|
5574
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
5575
|
+
}
|
|
5576
|
+
return out;
|
|
5577
|
+
}
|
|
5578
|
+
return null;
|
|
5579
|
+
}
|
|
5580
|
+
function methodFromOptions(objNode) {
|
|
5581
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5582
|
+
const pair = objNode.namedChild(i);
|
|
5583
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5584
|
+
const k = pair.childForFieldName("key");
|
|
5585
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
5586
|
+
if (kText !== "method") continue;
|
|
5587
|
+
const v = pair.childForFieldName("value");
|
|
5588
|
+
if (v && (v.type === "string" || v.type === "template_string")) {
|
|
5589
|
+
const s = stringText(v);
|
|
5590
|
+
return s ? s.toUpperCase() : void 0;
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
return void 0;
|
|
5594
|
+
}
|
|
5595
|
+
function stringText(node) {
|
|
5596
|
+
if (node.type === "string") {
|
|
5597
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5598
|
+
const child = node.namedChild(i);
|
|
5599
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5600
|
+
}
|
|
5601
|
+
return "";
|
|
5602
|
+
}
|
|
5603
|
+
return null;
|
|
5604
|
+
}
|
|
5605
|
+
function urlNodeFromConfig(objNode) {
|
|
5606
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5607
|
+
const pair = objNode.namedChild(i);
|
|
5608
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5609
|
+
const k = pair.childForFieldName("key");
|
|
5610
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
5611
|
+
if (kText === "url") return pair.childForFieldName("value");
|
|
5612
|
+
}
|
|
5613
|
+
return null;
|
|
5614
|
+
}
|
|
5615
|
+
function pathOf(urlStr) {
|
|
5616
|
+
try {
|
|
5617
|
+
const candidate = urlStr.startsWith("//") ? `http:${urlStr}` : urlStr;
|
|
5618
|
+
const parsed = new URL(candidate);
|
|
5619
|
+
return parsed.pathname || "/";
|
|
5620
|
+
} catch {
|
|
5621
|
+
return null;
|
|
5622
|
+
}
|
|
5623
|
+
}
|
|
5624
|
+
function matchHost(urlStr, knownHosts) {
|
|
5625
|
+
for (const host of knownHosts) {
|
|
5626
|
+
if (urlMatchesHost(urlStr, host)) return host;
|
|
5627
|
+
}
|
|
5628
|
+
return null;
|
|
5629
|
+
}
|
|
5630
|
+
function clientCallSitesFromSource(source, parser, knownHosts) {
|
|
5631
|
+
const tree = parseSource4(parser, source);
|
|
5632
|
+
const out = [];
|
|
5633
|
+
const push = (urlNode, method, callNode) => {
|
|
5634
|
+
const urlStr = reconstructUrl(urlNode);
|
|
5635
|
+
if (!urlStr) return;
|
|
5636
|
+
const host = matchHost(urlStr, knownHosts);
|
|
5637
|
+
if (!host) return;
|
|
5638
|
+
const p = pathOf(urlStr);
|
|
5639
|
+
if (p === null) return;
|
|
5640
|
+
const line = callNode.startPosition.row + 1;
|
|
5641
|
+
out.push({
|
|
5642
|
+
host,
|
|
5643
|
+
method,
|
|
5644
|
+
pathTemplate: p,
|
|
5645
|
+
line,
|
|
5646
|
+
snippet: snippet(source, line)
|
|
5647
|
+
});
|
|
5648
|
+
};
|
|
5649
|
+
walk2(tree.rootNode, (node) => {
|
|
5650
|
+
if (node.type !== "call_expression") return;
|
|
5651
|
+
const fn = node.childForFieldName("function");
|
|
5652
|
+
if (!fn) return;
|
|
5653
|
+
const args = node.childForFieldName("arguments");
|
|
5654
|
+
const first = args?.namedChild(0);
|
|
5655
|
+
if (!first) return;
|
|
5656
|
+
const fnName = fn.type === "identifier" ? fn.text : fn.type === "member_expression" ? fn.childForFieldName("property")?.text ?? "" : "";
|
|
5657
|
+
if (fn.type === "identifier" && fnName === "fetch") {
|
|
5658
|
+
const opts = args?.namedChild(1);
|
|
5659
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
5660
|
+
push(first, method, node);
|
|
5661
|
+
return;
|
|
5662
|
+
}
|
|
5663
|
+
if (fn.type === "identifier" && fnName === "axios") {
|
|
5664
|
+
if (first.type === "object") {
|
|
5665
|
+
const urlNode = urlNodeFromConfig(first);
|
|
5666
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
5667
|
+
} else {
|
|
5668
|
+
const opts = args?.namedChild(1);
|
|
5669
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
5670
|
+
push(first, method, node);
|
|
5671
|
+
}
|
|
5672
|
+
return;
|
|
5673
|
+
}
|
|
5674
|
+
if (fn.type === "member_expression") {
|
|
5675
|
+
const obj = fn.childForFieldName("object");
|
|
5676
|
+
const objName = obj?.text ?? "";
|
|
5677
|
+
if (objName === "axios" && AXIOS_METHODS.has(fnName)) {
|
|
5678
|
+
if (fnName === "request" && first.type === "object") {
|
|
5679
|
+
const urlNode = urlNodeFromConfig(first);
|
|
5680
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
5681
|
+
} else {
|
|
5682
|
+
push(first, fnName.toUpperCase(), node);
|
|
5683
|
+
}
|
|
5684
|
+
return;
|
|
5685
|
+
}
|
|
5686
|
+
if ((objName === "http" || objName === "https") && (fnName === "request" || fnName === "get")) {
|
|
5687
|
+
const opts = args?.namedChild(1);
|
|
5688
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? (fnName === "get" ? "GET" : "GET") : "GET";
|
|
5689
|
+
push(first, method, node);
|
|
5690
|
+
return;
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
});
|
|
5694
|
+
return out;
|
|
5695
|
+
}
|
|
5696
|
+
function buildRouteIndex(graph) {
|
|
5697
|
+
const index = /* @__PURE__ */ new Map();
|
|
5698
|
+
graph.forEachNode((_id, attrs) => {
|
|
5699
|
+
const node = attrs;
|
|
5700
|
+
if (node.type !== import_types13.NodeType.RouteNode) return;
|
|
5701
|
+
const route = attrs;
|
|
5702
|
+
const owner = (0, import_types13.serviceId)(route.service);
|
|
5703
|
+
const entry2 = {
|
|
5704
|
+
method: route.method.toUpperCase(),
|
|
5705
|
+
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
5706
|
+
routeNodeId: route.id
|
|
5707
|
+
};
|
|
5708
|
+
const list = index.get(owner);
|
|
5709
|
+
if (list) list.push(entry2);
|
|
5710
|
+
else index.set(owner, [entry2]);
|
|
5711
|
+
});
|
|
5712
|
+
return index;
|
|
5713
|
+
}
|
|
5714
|
+
function findRoute(entries, method, normalizedPath) {
|
|
5715
|
+
return entries.find(
|
|
5716
|
+
(e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || method === void 0 || e.method === method)
|
|
5717
|
+
);
|
|
5718
|
+
}
|
|
5719
|
+
async function addRouteCallEdges(graph, services) {
|
|
5720
|
+
const jsParser = makeJsParser4();
|
|
5721
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
5722
|
+
const routeIndex = buildRouteIndex(graph);
|
|
5723
|
+
if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
5724
|
+
let nodesAdded = 0;
|
|
5725
|
+
let edgesAdded = 0;
|
|
5726
|
+
for (const service of services) {
|
|
5727
|
+
const files = await loadSourceFiles(service.dir);
|
|
5728
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5729
|
+
for (const file of files) {
|
|
5730
|
+
if (isTestPath(file.path)) continue;
|
|
5731
|
+
if (!JS_CLIENT_EXTENSIONS.has(import_node_path24.default.extname(file.path))) continue;
|
|
5732
|
+
let sites;
|
|
5733
|
+
try {
|
|
5734
|
+
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
5735
|
+
} catch (err) {
|
|
5736
|
+
recordExtractionError("route-match call extraction", file.path, err);
|
|
5737
|
+
continue;
|
|
5738
|
+
}
|
|
5739
|
+
if (sites.length === 0) continue;
|
|
5740
|
+
const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
|
|
5741
|
+
for (const site of sites) {
|
|
5742
|
+
const serverServiceId = hostToNodeId.get(site.host);
|
|
5743
|
+
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
5744
|
+
const entries = routeIndex.get(serverServiceId);
|
|
5745
|
+
if (!entries) continue;
|
|
5746
|
+
const normalizedPath = normalizePathTemplate(site.pathTemplate);
|
|
5747
|
+
const match = findRoute(entries, site.method, normalizedPath);
|
|
5748
|
+
if (!match) continue;
|
|
5749
|
+
const dedupKey = `${relFile}|${match.routeNodeId}`;
|
|
5750
|
+
if (seen.has(dedupKey)) continue;
|
|
5751
|
+
seen.add(dedupKey);
|
|
5752
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5753
|
+
graph,
|
|
5754
|
+
service.pkg.name,
|
|
5755
|
+
service.node.id,
|
|
5756
|
+
relFile
|
|
5757
|
+
);
|
|
5758
|
+
nodesAdded += n;
|
|
5759
|
+
edgesAdded += e;
|
|
5760
|
+
const confidence = (0, import_types13.confidenceForExtracted)("verified-call-site");
|
|
5761
|
+
const ev = {
|
|
5762
|
+
file: relFile,
|
|
5763
|
+
line: site.line,
|
|
5764
|
+
snippet: site.snippet,
|
|
5765
|
+
method: site.method ?? match.method,
|
|
5766
|
+
pathTemplate: site.pathTemplate
|
|
5767
|
+
};
|
|
5768
|
+
if (!(0, import_types13.passesExtractedFloor)(confidence)) {
|
|
5769
|
+
noteExtractedDropped({
|
|
5770
|
+
source: fileNodeId,
|
|
5771
|
+
target: match.routeNodeId,
|
|
5772
|
+
type: import_types13.EdgeType.CALLS,
|
|
5773
|
+
confidence,
|
|
5774
|
+
confidenceKind: "verified-call-site",
|
|
5775
|
+
evidence: ev
|
|
5776
|
+
});
|
|
5777
|
+
continue;
|
|
5778
|
+
}
|
|
5779
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types13.EdgeType.CALLS);
|
|
5780
|
+
if (!graph.hasEdge(edgeId)) {
|
|
5781
|
+
const edge = {
|
|
5782
|
+
id: edgeId,
|
|
5783
|
+
source: fileNodeId,
|
|
5784
|
+
target: match.routeNodeId,
|
|
5785
|
+
type: import_types13.EdgeType.CALLS,
|
|
5786
|
+
provenance: import_types13.Provenance.EXTRACTED,
|
|
5787
|
+
confidence,
|
|
5788
|
+
evidence: ev
|
|
5789
|
+
};
|
|
5790
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, match.routeNodeId, edge);
|
|
5791
|
+
edgesAdded++;
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
}
|
|
5796
|
+
return { nodesAdded, edgesAdded };
|
|
5797
|
+
}
|
|
5798
|
+
|
|
4978
5799
|
// src/extract/calls/kafka.ts
|
|
4979
5800
|
init_cjs_shims();
|
|
4980
|
-
var
|
|
4981
|
-
var
|
|
5801
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
5802
|
+
var import_types14 = require("@neat.is/types");
|
|
4982
5803
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4983
5804
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
4984
5805
|
function findAll(re, text) {
|
|
@@ -4999,7 +5820,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4999
5820
|
seen.add(key);
|
|
5000
5821
|
const line = lineOf(file.content, topic);
|
|
5001
5822
|
out.push({
|
|
5002
|
-
infraId: (0,
|
|
5823
|
+
infraId: (0, import_types14.infraId)("kafka-topic", topic),
|
|
5003
5824
|
name: topic,
|
|
5004
5825
|
kind: "kafka-topic",
|
|
5005
5826
|
edgeType,
|
|
@@ -5008,7 +5829,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5008
5829
|
// tier (ADR-066).
|
|
5009
5830
|
confidenceKind: "verified-call-site",
|
|
5010
5831
|
evidence: {
|
|
5011
|
-
file:
|
|
5832
|
+
file: import_node_path25.default.relative(serviceDir, file.path),
|
|
5012
5833
|
line,
|
|
5013
5834
|
snippet: snippet(file.content, line)
|
|
5014
5835
|
}
|
|
@@ -5021,8 +5842,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5021
5842
|
|
|
5022
5843
|
// src/extract/calls/redis.ts
|
|
5023
5844
|
init_cjs_shims();
|
|
5024
|
-
var
|
|
5025
|
-
var
|
|
5845
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
5846
|
+
var import_types15 = require("@neat.is/types");
|
|
5026
5847
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
5027
5848
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
5028
5849
|
const out = [];
|
|
@@ -5035,7 +5856,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5035
5856
|
seen.add(host);
|
|
5036
5857
|
const line = lineOf(file.content, host);
|
|
5037
5858
|
out.push({
|
|
5038
|
-
infraId: (0,
|
|
5859
|
+
infraId: (0, import_types15.infraId)("redis", host),
|
|
5039
5860
|
name: host,
|
|
5040
5861
|
kind: "redis",
|
|
5041
5862
|
edgeType: "CALLS",
|
|
@@ -5044,7 +5865,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5044
5865
|
// support tier (ADR-066).
|
|
5045
5866
|
confidenceKind: "url-with-structural-support",
|
|
5046
5867
|
evidence: {
|
|
5047
|
-
file:
|
|
5868
|
+
file: import_node_path26.default.relative(serviceDir, file.path),
|
|
5048
5869
|
line,
|
|
5049
5870
|
snippet: snippet(file.content, line)
|
|
5050
5871
|
}
|
|
@@ -5055,8 +5876,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5055
5876
|
|
|
5056
5877
|
// src/extract/calls/aws.ts
|
|
5057
5878
|
init_cjs_shims();
|
|
5058
|
-
var
|
|
5059
|
-
var
|
|
5879
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
5880
|
+
var import_types16 = require("@neat.is/types");
|
|
5060
5881
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5061
5882
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5062
5883
|
function hasMarker(text, markers) {
|
|
@@ -5080,7 +5901,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5080
5901
|
seen.add(key);
|
|
5081
5902
|
const line = lineOf(file.content, name);
|
|
5082
5903
|
out.push({
|
|
5083
|
-
infraId: (0,
|
|
5904
|
+
infraId: (0, import_types16.infraId)(kind, name),
|
|
5084
5905
|
name,
|
|
5085
5906
|
kind,
|
|
5086
5907
|
edgeType: "CALLS",
|
|
@@ -5089,7 +5910,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5089
5910
|
// (ADR-066).
|
|
5090
5911
|
confidenceKind: "verified-call-site",
|
|
5091
5912
|
evidence: {
|
|
5092
|
-
file:
|
|
5913
|
+
file: import_node_path27.default.relative(serviceDir, file.path),
|
|
5093
5914
|
line,
|
|
5094
5915
|
snippet: snippet(file.content, line)
|
|
5095
5916
|
}
|
|
@@ -5114,8 +5935,8 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5114
5935
|
|
|
5115
5936
|
// src/extract/calls/grpc.ts
|
|
5116
5937
|
init_cjs_shims();
|
|
5117
|
-
var
|
|
5118
|
-
var
|
|
5938
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
5939
|
+
var import_types17 = require("@neat.is/types");
|
|
5119
5940
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
5120
5941
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
5121
5942
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -5164,7 +5985,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5164
5985
|
const { kind } = classified;
|
|
5165
5986
|
const line = lineOf(file.content, m[0]);
|
|
5166
5987
|
out.push({
|
|
5167
|
-
infraId: (0,
|
|
5988
|
+
infraId: (0, import_types17.infraId)(kind, name),
|
|
5168
5989
|
name,
|
|
5169
5990
|
kind,
|
|
5170
5991
|
edgeType: "CALLS",
|
|
@@ -5173,7 +5994,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5173
5994
|
// tier (ADR-066).
|
|
5174
5995
|
confidenceKind: "verified-call-site",
|
|
5175
5996
|
evidence: {
|
|
5176
|
-
file:
|
|
5997
|
+
file: import_node_path28.default.relative(serviceDir, file.path),
|
|
5177
5998
|
line,
|
|
5178
5999
|
snippet: snippet(file.content, line)
|
|
5179
6000
|
}
|
|
@@ -5184,8 +6005,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5184
6005
|
|
|
5185
6006
|
// src/extract/calls/supabase.ts
|
|
5186
6007
|
init_cjs_shims();
|
|
5187
|
-
var
|
|
5188
|
-
var
|
|
6008
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
6009
|
+
var import_types18 = require("@neat.is/types");
|
|
5189
6010
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
5190
6011
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
5191
6012
|
var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
|
|
@@ -5223,7 +6044,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5223
6044
|
seen.add(name);
|
|
5224
6045
|
const line = lineOf(file.content, m[0]);
|
|
5225
6046
|
out.push({
|
|
5226
|
-
infraId: (0,
|
|
6047
|
+
infraId: (0, import_types18.infraId)("supabase", name),
|
|
5227
6048
|
name,
|
|
5228
6049
|
kind: "supabase",
|
|
5229
6050
|
edgeType: "CALLS",
|
|
@@ -5233,7 +6054,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5233
6054
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
5234
6055
|
confidenceKind: "verified-call-site",
|
|
5235
6056
|
evidence: {
|
|
5236
|
-
file:
|
|
6057
|
+
file: import_node_path29.default.relative(serviceDir, file.path),
|
|
5237
6058
|
line,
|
|
5238
6059
|
snippet: snippet(file.content, line)
|
|
5239
6060
|
}
|
|
@@ -5246,11 +6067,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5246
6067
|
function edgeTypeFromEndpoint(ep) {
|
|
5247
6068
|
switch (ep.edgeType) {
|
|
5248
6069
|
case "PUBLISHES_TO":
|
|
5249
|
-
return
|
|
6070
|
+
return import_types19.EdgeType.PUBLISHES_TO;
|
|
5250
6071
|
case "CONSUMES_FROM":
|
|
5251
|
-
return
|
|
6072
|
+
return import_types19.EdgeType.CONSUMES_FROM;
|
|
5252
6073
|
default:
|
|
5253
|
-
return
|
|
6074
|
+
return import_types19.EdgeType.CALLS;
|
|
5254
6075
|
}
|
|
5255
6076
|
}
|
|
5256
6077
|
function isAwsKind(kind) {
|
|
@@ -5278,7 +6099,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5278
6099
|
if (!graph.hasNode(ep.infraId)) {
|
|
5279
6100
|
const node = {
|
|
5280
6101
|
id: ep.infraId,
|
|
5281
|
-
type:
|
|
6102
|
+
type: import_types19.NodeType.InfraNode,
|
|
5282
6103
|
name: ep.name,
|
|
5283
6104
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
5284
6105
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -5290,7 +6111,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5290
6111
|
nodesAdded++;
|
|
5291
6112
|
}
|
|
5292
6113
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
5293
|
-
const confidence = (0,
|
|
6114
|
+
const confidence = (0, import_types19.confidenceForExtracted)(ep.confidenceKind);
|
|
5294
6115
|
const relFile = toPosix2(ep.evidence.file);
|
|
5295
6116
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5296
6117
|
graph,
|
|
@@ -5300,7 +6121,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5300
6121
|
);
|
|
5301
6122
|
nodesAdded += n;
|
|
5302
6123
|
edgesAdded += e;
|
|
5303
|
-
if (!(0,
|
|
6124
|
+
if (!(0, import_types19.passesExtractedFloor)(confidence)) {
|
|
5304
6125
|
noteExtractedDropped({
|
|
5305
6126
|
source: fileNodeId,
|
|
5306
6127
|
target: ep.infraId,
|
|
@@ -5320,7 +6141,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5320
6141
|
source: fileNodeId,
|
|
5321
6142
|
target: ep.infraId,
|
|
5322
6143
|
type: edgeType,
|
|
5323
|
-
provenance:
|
|
6144
|
+
provenance: import_types19.Provenance.EXTRACTED,
|
|
5324
6145
|
confidence,
|
|
5325
6146
|
evidence: ep.evidence
|
|
5326
6147
|
};
|
|
@@ -5334,9 +6155,10 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5334
6155
|
async function addCallEdges(graph, services) {
|
|
5335
6156
|
const http = await addHttpCallEdges(graph, services);
|
|
5336
6157
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
6158
|
+
const routes = await addRouteCallEdges(graph, services);
|
|
5337
6159
|
return {
|
|
5338
|
-
nodesAdded: http.nodesAdded + ext.nodesAdded,
|
|
5339
|
-
edgesAdded: http.edgesAdded + ext.edgesAdded
|
|
6160
|
+
nodesAdded: http.nodesAdded + ext.nodesAdded + routes.nodesAdded,
|
|
6161
|
+
edgesAdded: http.edgesAdded + ext.edgesAdded + routes.edgesAdded
|
|
5340
6162
|
};
|
|
5341
6163
|
}
|
|
5342
6164
|
|
|
@@ -5345,16 +6167,16 @@ init_cjs_shims();
|
|
|
5345
6167
|
|
|
5346
6168
|
// src/extract/infra/docker-compose.ts
|
|
5347
6169
|
init_cjs_shims();
|
|
5348
|
-
var
|
|
5349
|
-
var
|
|
6170
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
6171
|
+
var import_types21 = require("@neat.is/types");
|
|
5350
6172
|
|
|
5351
6173
|
// src/extract/infra/shared.ts
|
|
5352
6174
|
init_cjs_shims();
|
|
5353
|
-
var
|
|
6175
|
+
var import_types20 = require("@neat.is/types");
|
|
5354
6176
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
5355
6177
|
return {
|
|
5356
|
-
id: (0,
|
|
5357
|
-
type:
|
|
6178
|
+
id: (0, import_types20.infraId)(kind, name),
|
|
6179
|
+
type: import_types20.NodeType.InfraNode,
|
|
5358
6180
|
name,
|
|
5359
6181
|
provider,
|
|
5360
6182
|
kind,
|
|
@@ -5383,7 +6205,7 @@ function dependsOnList(value) {
|
|
|
5383
6205
|
}
|
|
5384
6206
|
function serviceNameToServiceNode(name, services) {
|
|
5385
6207
|
for (const s of services) {
|
|
5386
|
-
if (s.node.name === name ||
|
|
6208
|
+
if (s.node.name === name || import_node_path30.default.basename(s.dir) === name) return s.node.id;
|
|
5387
6209
|
}
|
|
5388
6210
|
return null;
|
|
5389
6211
|
}
|
|
@@ -5392,7 +6214,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5392
6214
|
let edgesAdded = 0;
|
|
5393
6215
|
let composePath = null;
|
|
5394
6216
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
5395
|
-
const abs =
|
|
6217
|
+
const abs = import_node_path30.default.join(scanPath, name);
|
|
5396
6218
|
if (await exists(abs)) {
|
|
5397
6219
|
composePath = abs;
|
|
5398
6220
|
break;
|
|
@@ -5405,13 +6227,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5405
6227
|
} catch (err) {
|
|
5406
6228
|
recordExtractionError(
|
|
5407
6229
|
"infra docker-compose",
|
|
5408
|
-
|
|
6230
|
+
import_node_path30.default.relative(scanPath, composePath),
|
|
5409
6231
|
err
|
|
5410
6232
|
);
|
|
5411
6233
|
return { nodesAdded, edgesAdded };
|
|
5412
6234
|
}
|
|
5413
6235
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
5414
|
-
const evidenceFile =
|
|
6236
|
+
const evidenceFile = import_node_path30.default.relative(scanPath, composePath).split(import_node_path30.default.sep).join("/");
|
|
5415
6237
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
5416
6238
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
5417
6239
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -5433,15 +6255,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5433
6255
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
5434
6256
|
const targetId = composeNameToNodeId.get(dep);
|
|
5435
6257
|
if (!targetId) continue;
|
|
5436
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
6258
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types21.EdgeType.DEPENDS_ON);
|
|
5437
6259
|
if (graph.hasEdge(edgeId)) continue;
|
|
5438
6260
|
const edge = {
|
|
5439
6261
|
id: edgeId,
|
|
5440
6262
|
source: sourceId,
|
|
5441
6263
|
target: targetId,
|
|
5442
|
-
type:
|
|
5443
|
-
provenance:
|
|
5444
|
-
confidence: (0,
|
|
6264
|
+
type: import_types21.EdgeType.DEPENDS_ON,
|
|
6265
|
+
provenance: import_types21.Provenance.EXTRACTED,
|
|
6266
|
+
confidence: (0, import_types21.confidenceForExtracted)("structural"),
|
|
5445
6267
|
evidence: { file: evidenceFile }
|
|
5446
6268
|
};
|
|
5447
6269
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -5453,9 +6275,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5453
6275
|
|
|
5454
6276
|
// src/extract/infra/dockerfile.ts
|
|
5455
6277
|
init_cjs_shims();
|
|
5456
|
-
var
|
|
6278
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
5457
6279
|
var import_node_fs15 = require("fs");
|
|
5458
|
-
var
|
|
6280
|
+
var import_types22 = require("@neat.is/types");
|
|
5459
6281
|
function readDockerfile(content) {
|
|
5460
6282
|
let image = null;
|
|
5461
6283
|
const ports = [];
|
|
@@ -5484,7 +6306,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5484
6306
|
let nodesAdded = 0;
|
|
5485
6307
|
let edgesAdded = 0;
|
|
5486
6308
|
for (const service of services) {
|
|
5487
|
-
const dockerfilePath =
|
|
6309
|
+
const dockerfilePath = import_node_path31.default.join(service.dir, "Dockerfile");
|
|
5488
6310
|
if (!await exists(dockerfilePath)) continue;
|
|
5489
6311
|
let content;
|
|
5490
6312
|
try {
|
|
@@ -5492,7 +6314,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5492
6314
|
} catch (err) {
|
|
5493
6315
|
recordExtractionError(
|
|
5494
6316
|
"infra dockerfile",
|
|
5495
|
-
|
|
6317
|
+
import_node_path31.default.relative(scanPath, dockerfilePath),
|
|
5496
6318
|
err
|
|
5497
6319
|
);
|
|
5498
6320
|
continue;
|
|
@@ -5504,8 +6326,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5504
6326
|
graph.addNode(node.id, node);
|
|
5505
6327
|
nodesAdded++;
|
|
5506
6328
|
}
|
|
5507
|
-
const relDockerfile = toPosix2(
|
|
5508
|
-
const evidenceFile = toPosix2(
|
|
6329
|
+
const relDockerfile = toPosix2(import_node_path31.default.relative(service.dir, dockerfilePath));
|
|
6330
|
+
const evidenceFile = toPosix2(import_node_path31.default.relative(scanPath, dockerfilePath));
|
|
5509
6331
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
5510
6332
|
graph,
|
|
5511
6333
|
service.pkg.name,
|
|
@@ -5514,15 +6336,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5514
6336
|
);
|
|
5515
6337
|
nodesAdded += fn;
|
|
5516
6338
|
edgesAdded += fe;
|
|
5517
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id,
|
|
6339
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types22.EdgeType.RUNS_ON);
|
|
5518
6340
|
if (!graph.hasEdge(edgeId)) {
|
|
5519
6341
|
const edge = {
|
|
5520
6342
|
id: edgeId,
|
|
5521
6343
|
source: fileNodeId,
|
|
5522
6344
|
target: node.id,
|
|
5523
|
-
type:
|
|
5524
|
-
provenance:
|
|
5525
|
-
confidence: (0,
|
|
6345
|
+
type: import_types22.EdgeType.RUNS_ON,
|
|
6346
|
+
provenance: import_types22.Provenance.EXTRACTED,
|
|
6347
|
+
confidence: (0, import_types22.confidenceForExtracted)("structural"),
|
|
5526
6348
|
evidence: {
|
|
5527
6349
|
file: evidenceFile,
|
|
5528
6350
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -5537,15 +6359,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5537
6359
|
graph.addNode(portNode.id, portNode);
|
|
5538
6360
|
nodesAdded++;
|
|
5539
6361
|
}
|
|
5540
|
-
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id,
|
|
6362
|
+
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types22.EdgeType.CONNECTS_TO);
|
|
5541
6363
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
5542
6364
|
const portEdge = {
|
|
5543
6365
|
id: portEdgeId,
|
|
5544
6366
|
source: fileNodeId,
|
|
5545
6367
|
target: portNode.id,
|
|
5546
|
-
type:
|
|
5547
|
-
provenance:
|
|
5548
|
-
confidence: (0,
|
|
6368
|
+
type: import_types22.EdgeType.CONNECTS_TO,
|
|
6369
|
+
provenance: import_types22.Provenance.EXTRACTED,
|
|
6370
|
+
confidence: (0, import_types22.confidenceForExtracted)("structural"),
|
|
5549
6371
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
5550
6372
|
};
|
|
5551
6373
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -5558,8 +6380,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5558
6380
|
// src/extract/infra/terraform.ts
|
|
5559
6381
|
init_cjs_shims();
|
|
5560
6382
|
var import_node_fs16 = require("fs");
|
|
5561
|
-
var
|
|
5562
|
-
var
|
|
6383
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
6384
|
+
var import_types23 = require("@neat.is/types");
|
|
5563
6385
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
5564
6386
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
5565
6387
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
@@ -5569,11 +6391,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
|
|
|
5569
6391
|
for (const entry2 of entries) {
|
|
5570
6392
|
if (entry2.isDirectory()) {
|
|
5571
6393
|
if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
|
|
5572
|
-
const child =
|
|
6394
|
+
const child = import_node_path32.default.join(start, entry2.name);
|
|
5573
6395
|
if (await isPythonVenvDir(child)) continue;
|
|
5574
6396
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
5575
6397
|
} else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
|
|
5576
|
-
out.push(
|
|
6398
|
+
out.push(import_node_path32.default.join(start, entry2.name));
|
|
5577
6399
|
}
|
|
5578
6400
|
}
|
|
5579
6401
|
return out;
|
|
@@ -5605,7 +6427,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5605
6427
|
const files = await walkTfFiles(scanPath);
|
|
5606
6428
|
for (const file of files) {
|
|
5607
6429
|
const content = await import_node_fs16.promises.readFile(file, "utf8");
|
|
5608
|
-
const evidenceFile = toPosix2(
|
|
6430
|
+
const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, file));
|
|
5609
6431
|
const resources = [];
|
|
5610
6432
|
const byKey = /* @__PURE__ */ new Map();
|
|
5611
6433
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -5640,16 +6462,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5640
6462
|
if (!target) continue;
|
|
5641
6463
|
if (seen.has(target.nodeId)) continue;
|
|
5642
6464
|
seen.add(target.nodeId);
|
|
5643
|
-
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
6465
|
+
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types23.EdgeType.DEPENDS_ON);
|
|
5644
6466
|
if (graph.hasEdge(edgeId)) continue;
|
|
5645
6467
|
const line = lineAt(content, resource.bodyOffset + ref.index);
|
|
5646
6468
|
const edge = {
|
|
5647
6469
|
id: edgeId,
|
|
5648
6470
|
source: resource.nodeId,
|
|
5649
6471
|
target: target.nodeId,
|
|
5650
|
-
type:
|
|
5651
|
-
provenance:
|
|
5652
|
-
confidence: (0,
|
|
6472
|
+
type: import_types23.EdgeType.DEPENDS_ON,
|
|
6473
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
6474
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
5653
6475
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
5654
6476
|
};
|
|
5655
6477
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -5663,7 +6485,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5663
6485
|
// src/extract/infra/k8s.ts
|
|
5664
6486
|
init_cjs_shims();
|
|
5665
6487
|
var import_node_fs17 = require("fs");
|
|
5666
|
-
var
|
|
6488
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
5667
6489
|
var import_yaml3 = require("yaml");
|
|
5668
6490
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
5669
6491
|
Service: "k8s-service",
|
|
@@ -5681,11 +6503,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
|
5681
6503
|
for (const entry2 of entries) {
|
|
5682
6504
|
if (entry2.isDirectory()) {
|
|
5683
6505
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
5684
|
-
const child =
|
|
6506
|
+
const child = import_node_path33.default.join(start, entry2.name);
|
|
5685
6507
|
if (await isPythonVenvDir(child)) continue;
|
|
5686
6508
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
5687
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
5688
|
-
out.push(
|
|
6509
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path33.default.extname(entry2.name))) {
|
|
6510
|
+
out.push(import_node_path33.default.join(start, entry2.name));
|
|
5689
6511
|
}
|
|
5690
6512
|
}
|
|
5691
6513
|
return out;
|
|
@@ -5729,17 +6551,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
5729
6551
|
}
|
|
5730
6552
|
|
|
5731
6553
|
// src/extract/index.ts
|
|
5732
|
-
var
|
|
6554
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5733
6555
|
|
|
5734
6556
|
// src/extract/retire.ts
|
|
5735
6557
|
init_cjs_shims();
|
|
5736
6558
|
var import_node_fs18 = require("fs");
|
|
5737
|
-
var
|
|
5738
|
-
var
|
|
6559
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
6560
|
+
var import_types24 = require("@neat.is/types");
|
|
5739
6561
|
function dropOrphanedFileNodes(graph) {
|
|
5740
6562
|
const orphans = [];
|
|
5741
6563
|
graph.forEachNode((id, attrs) => {
|
|
5742
|
-
if (attrs.type !==
|
|
6564
|
+
if (attrs.type !== import_types24.NodeType.FileNode) return;
|
|
5743
6565
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
5744
6566
|
orphans.push(id);
|
|
5745
6567
|
}
|
|
@@ -5752,14 +6574,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
5752
6574
|
const bases = [scanPath, ...serviceDirs];
|
|
5753
6575
|
graph.forEachEdge((id, attrs) => {
|
|
5754
6576
|
const edge = attrs;
|
|
5755
|
-
if (edge.provenance !==
|
|
6577
|
+
if (edge.provenance !== import_types24.Provenance.EXTRACTED) return;
|
|
5756
6578
|
const evidenceFile = edge.evidence?.file;
|
|
5757
6579
|
if (!evidenceFile) return;
|
|
5758
|
-
if (
|
|
6580
|
+
if (import_node_path34.default.isAbsolute(evidenceFile)) {
|
|
5759
6581
|
if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
|
|
5760
6582
|
return;
|
|
5761
6583
|
}
|
|
5762
|
-
const found = bases.some((base) => (0, import_node_fs18.existsSync)(
|
|
6584
|
+
const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path34.default.join(base, evidenceFile)));
|
|
5763
6585
|
if (!found) toDrop.push(id);
|
|
5764
6586
|
});
|
|
5765
6587
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -5778,6 +6600,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5778
6600
|
const importGraph = await addImports(graph, services);
|
|
5779
6601
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
5780
6602
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
6603
|
+
const routePhase = await addRoutes(graph, services);
|
|
5781
6604
|
const phase4 = await addCallEdges(graph, services);
|
|
5782
6605
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
5783
6606
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
@@ -5799,7 +6622,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5799
6622
|
}
|
|
5800
6623
|
const droppedEntries = drainDroppedExtracted();
|
|
5801
6624
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
5802
|
-
const rejectedPath =
|
|
6625
|
+
const rejectedPath = import_node_path35.default.join(import_node_path35.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
5803
6626
|
try {
|
|
5804
6627
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
5805
6628
|
} catch (err) {
|
|
@@ -5809,8 +6632,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5809
6632
|
}
|
|
5810
6633
|
}
|
|
5811
6634
|
const result = {
|
|
5812
|
-
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
5813
|
-
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
6635
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
6636
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
5814
6637
|
frontiersPromoted,
|
|
5815
6638
|
extractionErrors: errorEntries.length,
|
|
5816
6639
|
errorEntries,
|
|
@@ -5834,8 +6657,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5834
6657
|
// src/persist.ts
|
|
5835
6658
|
init_cjs_shims();
|
|
5836
6659
|
var import_node_fs19 = require("fs");
|
|
5837
|
-
var
|
|
5838
|
-
var
|
|
6660
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
6661
|
+
var import_types25 = require("@neat.is/types");
|
|
5839
6662
|
var SCHEMA_VERSION = 4;
|
|
5840
6663
|
function migrateV1ToV2(payload) {
|
|
5841
6664
|
const nodes = payload.graph.nodes;
|
|
@@ -5857,12 +6680,12 @@ function migrateV2ToV3(payload) {
|
|
|
5857
6680
|
for (const edge of edges) {
|
|
5858
6681
|
const attrs = edge.attributes;
|
|
5859
6682
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
5860
|
-
attrs.provenance =
|
|
6683
|
+
attrs.provenance = import_types25.Provenance.OBSERVED;
|
|
5861
6684
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
5862
6685
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
5863
6686
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
5864
6687
|
if (type && source && target) {
|
|
5865
|
-
const newId = (0,
|
|
6688
|
+
const newId = (0, import_types25.observedEdgeId)(source, target, type);
|
|
5866
6689
|
attrs.id = newId;
|
|
5867
6690
|
if (edge.key) edge.key = newId;
|
|
5868
6691
|
}
|
|
@@ -5871,7 +6694,7 @@ function migrateV2ToV3(payload) {
|
|
|
5871
6694
|
return { ...payload, schemaVersion: 3 };
|
|
5872
6695
|
}
|
|
5873
6696
|
async function ensureDir(filePath) {
|
|
5874
|
-
await import_node_fs19.promises.mkdir(
|
|
6697
|
+
await import_node_fs19.promises.mkdir(import_node_path36.default.dirname(filePath), { recursive: true });
|
|
5875
6698
|
}
|
|
5876
6699
|
async function saveGraphToDisk(graph, outPath) {
|
|
5877
6700
|
await ensureDir(outPath);
|
|
@@ -5952,23 +6775,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
5952
6775
|
|
|
5953
6776
|
// src/projects.ts
|
|
5954
6777
|
init_cjs_shims();
|
|
5955
|
-
var
|
|
6778
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
5956
6779
|
function pathsForProject(project, baseDir) {
|
|
5957
6780
|
if (project === DEFAULT_PROJECT) {
|
|
5958
6781
|
return {
|
|
5959
|
-
snapshotPath:
|
|
5960
|
-
errorsPath:
|
|
5961
|
-
staleEventsPath:
|
|
5962
|
-
embeddingsCachePath:
|
|
5963
|
-
policyViolationsPath:
|
|
6782
|
+
snapshotPath: import_node_path37.default.join(baseDir, "graph.json"),
|
|
6783
|
+
errorsPath: import_node_path37.default.join(baseDir, "errors.ndjson"),
|
|
6784
|
+
staleEventsPath: import_node_path37.default.join(baseDir, "stale-events.ndjson"),
|
|
6785
|
+
embeddingsCachePath: import_node_path37.default.join(baseDir, "embeddings.json"),
|
|
6786
|
+
policyViolationsPath: import_node_path37.default.join(baseDir, "policy-violations.ndjson")
|
|
5964
6787
|
};
|
|
5965
6788
|
}
|
|
5966
6789
|
return {
|
|
5967
|
-
snapshotPath:
|
|
5968
|
-
errorsPath:
|
|
5969
|
-
staleEventsPath:
|
|
5970
|
-
embeddingsCachePath:
|
|
5971
|
-
policyViolationsPath:
|
|
6790
|
+
snapshotPath: import_node_path37.default.join(baseDir, `${project}.json`),
|
|
6791
|
+
errorsPath: import_node_path37.default.join(baseDir, `errors.${project}.ndjson`),
|
|
6792
|
+
staleEventsPath: import_node_path37.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
6793
|
+
embeddingsCachePath: import_node_path37.default.join(baseDir, `embeddings.${project}.json`),
|
|
6794
|
+
policyViolationsPath: import_node_path37.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
5972
6795
|
};
|
|
5973
6796
|
}
|
|
5974
6797
|
var Projects = class {
|
|
@@ -6006,19 +6829,19 @@ var Projects = class {
|
|
|
6006
6829
|
init_cjs_shims();
|
|
6007
6830
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
6008
6831
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
6009
|
-
var
|
|
6832
|
+
var import_types28 = require("@neat.is/types");
|
|
6010
6833
|
|
|
6011
6834
|
// src/extend/index.ts
|
|
6012
6835
|
init_cjs_shims();
|
|
6013
6836
|
var import_node_fs21 = require("fs");
|
|
6014
|
-
var
|
|
6837
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
6015
6838
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
6016
6839
|
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
6017
6840
|
|
|
6018
6841
|
// src/installers/package-manager.ts
|
|
6019
6842
|
init_cjs_shims();
|
|
6020
6843
|
var import_node_fs20 = require("fs");
|
|
6021
|
-
var
|
|
6844
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
6022
6845
|
var import_node_child_process = require("child_process");
|
|
6023
6846
|
var LOCKFILE_PRIORITY = [
|
|
6024
6847
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -6040,22 +6863,22 @@ async function exists2(p) {
|
|
|
6040
6863
|
}
|
|
6041
6864
|
}
|
|
6042
6865
|
async function detectPackageManager(serviceDir) {
|
|
6043
|
-
let dir =
|
|
6866
|
+
let dir = import_node_path38.default.resolve(serviceDir);
|
|
6044
6867
|
const stops = /* @__PURE__ */ new Set();
|
|
6045
6868
|
for (let i = 0; i < 64; i++) {
|
|
6046
6869
|
if (stops.has(dir)) break;
|
|
6047
6870
|
stops.add(dir);
|
|
6048
6871
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
6049
|
-
const lockPath =
|
|
6872
|
+
const lockPath = import_node_path38.default.join(dir, candidate.lockfile);
|
|
6050
6873
|
if (await exists2(lockPath)) {
|
|
6051
6874
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
6052
6875
|
}
|
|
6053
6876
|
}
|
|
6054
|
-
const parent =
|
|
6877
|
+
const parent = import_node_path38.default.dirname(dir);
|
|
6055
6878
|
if (parent === dir) break;
|
|
6056
6879
|
dir = parent;
|
|
6057
6880
|
}
|
|
6058
|
-
return { pm: "npm", cwd:
|
|
6881
|
+
return { pm: "npm", cwd: import_node_path38.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
6059
6882
|
}
|
|
6060
6883
|
async function runPackageManagerInstall(cmd) {
|
|
6061
6884
|
return new Promise((resolve) => {
|
|
@@ -6104,7 +6927,7 @@ async function fileExists2(p) {
|
|
|
6104
6927
|
}
|
|
6105
6928
|
}
|
|
6106
6929
|
async function readPackageJson(scanPath) {
|
|
6107
|
-
const pkgPath =
|
|
6930
|
+
const pkgPath = import_node_path39.default.join(scanPath, "package.json");
|
|
6108
6931
|
const raw = await import_node_fs21.promises.readFile(pkgPath, "utf8");
|
|
6109
6932
|
return JSON.parse(raw);
|
|
6110
6933
|
}
|
|
@@ -6115,11 +6938,11 @@ async function findHookFiles(scanPath) {
|
|
|
6115
6938
|
).sort();
|
|
6116
6939
|
}
|
|
6117
6940
|
function extendLogPath() {
|
|
6118
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
6941
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path39.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
6119
6942
|
}
|
|
6120
6943
|
async function appendExtendLog(entry2) {
|
|
6121
6944
|
const logPath = extendLogPath();
|
|
6122
|
-
await import_node_fs21.promises.mkdir(
|
|
6945
|
+
await import_node_fs21.promises.mkdir(import_node_path39.default.dirname(logPath), { recursive: true });
|
|
6123
6946
|
await import_node_fs21.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
|
|
6124
6947
|
}
|
|
6125
6948
|
function splicedContent(fileContent, snippet2) {
|
|
@@ -6178,7 +7001,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
6178
7001
|
}
|
|
6179
7002
|
async function describeProjectInstrumentation(ctx) {
|
|
6180
7003
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
6181
|
-
const envNeat = await fileExists2(
|
|
7004
|
+
const envNeat = await fileExists2(import_node_path39.default.join(ctx.scanPath, ".env.neat"));
|
|
6182
7005
|
const registryInstrPackages = new Set(
|
|
6183
7006
|
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
6184
7007
|
);
|
|
@@ -6200,16 +7023,16 @@ async function applyExtension(ctx, args, options) {
|
|
|
6200
7023
|
);
|
|
6201
7024
|
}
|
|
6202
7025
|
for (const file of hookFiles) {
|
|
6203
|
-
const content = await import_node_fs21.promises.readFile(
|
|
7026
|
+
const content = await import_node_fs21.promises.readFile(import_node_path39.default.join(ctx.scanPath, file), "utf8");
|
|
6204
7027
|
if (content.includes(args.registration_snippet)) {
|
|
6205
7028
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
6206
7029
|
}
|
|
6207
7030
|
}
|
|
6208
7031
|
const primaryFile = hookFiles[0];
|
|
6209
|
-
const primaryPath =
|
|
7032
|
+
const primaryPath = import_node_path39.default.join(ctx.scanPath, primaryFile);
|
|
6210
7033
|
const filesTouched = [];
|
|
6211
7034
|
const depsAdded = [];
|
|
6212
|
-
const pkgPath =
|
|
7035
|
+
const pkgPath = import_node_path39.default.join(ctx.scanPath, "package.json");
|
|
6213
7036
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
6214
7037
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
6215
7038
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
@@ -6255,7 +7078,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
6255
7078
|
};
|
|
6256
7079
|
}
|
|
6257
7080
|
for (const file of hookFiles) {
|
|
6258
|
-
const content = await import_node_fs21.promises.readFile(
|
|
7081
|
+
const content = await import_node_fs21.promises.readFile(import_node_path39.default.join(ctx.scanPath, file), "utf8");
|
|
6259
7082
|
if (content.includes(args.registration_snippet)) {
|
|
6260
7083
|
return {
|
|
6261
7084
|
library: args.library,
|
|
@@ -6277,7 +7100,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
6277
7100
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
6278
7101
|
filesTouched.push("package.json");
|
|
6279
7102
|
}
|
|
6280
|
-
const hookContent = await import_node_fs21.promises.readFile(
|
|
7103
|
+
const hookContent = await import_node_fs21.promises.readFile(import_node_path39.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
6281
7104
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
6282
7105
|
if (patched) {
|
|
6283
7106
|
filesTouched.push(primaryFile);
|
|
@@ -6298,7 +7121,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
6298
7121
|
if (!match) {
|
|
6299
7122
|
return { undone: false, message: "no apply found for library" };
|
|
6300
7123
|
}
|
|
6301
|
-
const pkgPath =
|
|
7124
|
+
const pkgPath = import_node_path39.default.join(ctx.scanPath, "package.json");
|
|
6302
7125
|
if (await fileExists2(pkgPath)) {
|
|
6303
7126
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
6304
7127
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
@@ -6309,7 +7132,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
6309
7132
|
}
|
|
6310
7133
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
6311
7134
|
for (const file of hookFiles) {
|
|
6312
|
-
const filePath =
|
|
7135
|
+
const filePath = import_node_path39.default.join(ctx.scanPath, file);
|
|
6313
7136
|
const content = await import_node_fs21.promises.readFile(filePath, "utf8");
|
|
6314
7137
|
if (content.includes(match.registration_snippet)) {
|
|
6315
7138
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
@@ -6325,7 +7148,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
6325
7148
|
|
|
6326
7149
|
// src/divergences.ts
|
|
6327
7150
|
init_cjs_shims();
|
|
6328
|
-
var
|
|
7151
|
+
var import_types26 = require("@neat.is/types");
|
|
6329
7152
|
function bucketKey(source, target, type) {
|
|
6330
7153
|
return `${type}|${source}|${target}`;
|
|
6331
7154
|
}
|
|
@@ -6333,22 +7156,22 @@ function bucketEdges(graph) {
|
|
|
6333
7156
|
const buckets = /* @__PURE__ */ new Map();
|
|
6334
7157
|
graph.forEachEdge((id, attrs) => {
|
|
6335
7158
|
const e = attrs;
|
|
6336
|
-
const parsed = (0,
|
|
7159
|
+
const parsed = (0, import_types26.parseEdgeId)(id);
|
|
6337
7160
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
6338
7161
|
const key = bucketKey(e.source, e.target, e.type);
|
|
6339
7162
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
6340
7163
|
switch (provenance) {
|
|
6341
|
-
case
|
|
7164
|
+
case import_types26.Provenance.EXTRACTED:
|
|
6342
7165
|
cur.extracted = e;
|
|
6343
7166
|
break;
|
|
6344
|
-
case
|
|
7167
|
+
case import_types26.Provenance.OBSERVED:
|
|
6345
7168
|
cur.observed = e;
|
|
6346
7169
|
break;
|
|
6347
|
-
case
|
|
7170
|
+
case import_types26.Provenance.INFERRED:
|
|
6348
7171
|
cur.inferred = e;
|
|
6349
7172
|
break;
|
|
6350
7173
|
default:
|
|
6351
|
-
if (e.provenance ===
|
|
7174
|
+
if (e.provenance === import_types26.Provenance.STALE) cur.stale = e;
|
|
6352
7175
|
}
|
|
6353
7176
|
buckets.set(key, cur);
|
|
6354
7177
|
});
|
|
@@ -6357,7 +7180,7 @@ function bucketEdges(graph) {
|
|
|
6357
7180
|
function nodeIsFrontier(graph, nodeId) {
|
|
6358
7181
|
if (!graph.hasNode(nodeId)) return false;
|
|
6359
7182
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
6360
|
-
return attrs.type ===
|
|
7183
|
+
return attrs.type === import_types26.NodeType.FrontierNode;
|
|
6361
7184
|
}
|
|
6362
7185
|
function clampConfidence(n) {
|
|
6363
7186
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -6377,14 +7200,14 @@ function gradedConfidence(edge) {
|
|
|
6377
7200
|
return clampConfidence(confidenceForEdge(edge));
|
|
6378
7201
|
}
|
|
6379
7202
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
7203
|
+
import_types26.EdgeType.CALLS,
|
|
7204
|
+
import_types26.EdgeType.CONNECTS_TO,
|
|
7205
|
+
import_types26.EdgeType.PUBLISHES_TO,
|
|
7206
|
+
import_types26.EdgeType.CONSUMES_FROM
|
|
6384
7207
|
]);
|
|
6385
7208
|
function detectMissingDivergences(graph, bucket) {
|
|
6386
7209
|
const out = [];
|
|
6387
|
-
if (bucket.type ===
|
|
7210
|
+
if (bucket.type === import_types26.EdgeType.CONTAINS) return out;
|
|
6388
7211
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
6389
7212
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
6390
7213
|
out.push({
|
|
@@ -6425,7 +7248,7 @@ function declaredHostFor(svc) {
|
|
|
6425
7248
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
6426
7249
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6427
7250
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6428
|
-
if (e.type ===
|
|
7251
|
+
if (e.type === import_types26.EdgeType.CONFIGURED_BY && e.provenance === import_types26.Provenance.EXTRACTED) {
|
|
6429
7252
|
return true;
|
|
6430
7253
|
}
|
|
6431
7254
|
}
|
|
@@ -6438,10 +7261,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
6438
7261
|
const out = [];
|
|
6439
7262
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6440
7263
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6441
|
-
if (edge.type !==
|
|
6442
|
-
if (edge.provenance !==
|
|
7264
|
+
if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
|
|
7265
|
+
if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
|
|
6443
7266
|
const target = graph.getNodeAttributes(edge.target);
|
|
6444
|
-
if (target.type !==
|
|
7267
|
+
if (target.type !== import_types26.NodeType.DatabaseNode) continue;
|
|
6445
7268
|
const observedHost = target.host?.trim();
|
|
6446
7269
|
if (!observedHost) continue;
|
|
6447
7270
|
if (observedHost === declaredHost) continue;
|
|
@@ -6463,10 +7286,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
6463
7286
|
const deps = svc.dependencies ?? {};
|
|
6464
7287
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6465
7288
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6466
|
-
if (edge.type !==
|
|
6467
|
-
if (edge.provenance !==
|
|
7289
|
+
if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
|
|
7290
|
+
if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
|
|
6468
7291
|
const target = graph.getNodeAttributes(edge.target);
|
|
6469
|
-
if (target.type !==
|
|
7292
|
+
if (target.type !== import_types26.NodeType.DatabaseNode) continue;
|
|
6470
7293
|
for (const pair of compatPairs()) {
|
|
6471
7294
|
if (pair.engine !== target.engine) continue;
|
|
6472
7295
|
const declared = deps[pair.driver];
|
|
@@ -6525,7 +7348,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
6525
7348
|
for (const d of all) {
|
|
6526
7349
|
if (d.type !== "host-mismatch") continue;
|
|
6527
7350
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
6528
|
-
declaredHalf.add((0,
|
|
7351
|
+
declaredHalf.add((0, import_types26.databaseId)(d.extractedHost));
|
|
6529
7352
|
}
|
|
6530
7353
|
if (observedHalf.size === 0) return all;
|
|
6531
7354
|
return all.filter((d) => {
|
|
@@ -6544,7 +7367,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6544
7367
|
}
|
|
6545
7368
|
graph.forEachNode((nodeId, attrs) => {
|
|
6546
7369
|
const n = attrs;
|
|
6547
|
-
if (n.type !==
|
|
7370
|
+
if (n.type !== import_types26.NodeType.ServiceNode) return;
|
|
6548
7371
|
const svc = n;
|
|
6549
7372
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
6550
7373
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -6578,7 +7401,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6578
7401
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
6579
7402
|
return a.target.localeCompare(b.target);
|
|
6580
7403
|
});
|
|
6581
|
-
return
|
|
7404
|
+
return import_types26.DivergenceResultSchema.parse({
|
|
6582
7405
|
divergences: filtered,
|
|
6583
7406
|
totalAffected: filtered.length,
|
|
6584
7407
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -6666,23 +7489,23 @@ function canonicalJson(value) {
|
|
|
6666
7489
|
init_cjs_shims();
|
|
6667
7490
|
var import_node_fs23 = require("fs");
|
|
6668
7491
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
6669
|
-
var
|
|
6670
|
-
var
|
|
7492
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
7493
|
+
var import_types27 = require("@neat.is/types");
|
|
6671
7494
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
6672
7495
|
var LOCK_RETRY_MS = 50;
|
|
6673
7496
|
function neatHome() {
|
|
6674
7497
|
const override = process.env.NEAT_HOME;
|
|
6675
|
-
if (override && override.length > 0) return
|
|
6676
|
-
return
|
|
7498
|
+
if (override && override.length > 0) return import_node_path40.default.resolve(override);
|
|
7499
|
+
return import_node_path40.default.join(import_node_os3.default.homedir(), ".neat");
|
|
6677
7500
|
}
|
|
6678
7501
|
function registryPath() {
|
|
6679
|
-
return
|
|
7502
|
+
return import_node_path40.default.join(neatHome(), "projects.json");
|
|
6680
7503
|
}
|
|
6681
7504
|
function registryLockPath() {
|
|
6682
|
-
return
|
|
7505
|
+
return import_node_path40.default.join(neatHome(), "projects.json.lock");
|
|
6683
7506
|
}
|
|
6684
7507
|
function daemonPidPath() {
|
|
6685
|
-
return
|
|
7508
|
+
return import_node_path40.default.join(neatHome(), "neatd.pid");
|
|
6686
7509
|
}
|
|
6687
7510
|
function isPidAliveDefault(pid) {
|
|
6688
7511
|
try {
|
|
@@ -6742,7 +7565,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
6742
7565
|
}
|
|
6743
7566
|
}
|
|
6744
7567
|
async function writeAtomically(target, contents) {
|
|
6745
|
-
await import_node_fs23.promises.mkdir(
|
|
7568
|
+
await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
|
|
6746
7569
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
6747
7570
|
const fd = await import_node_fs23.promises.open(tmp, "w");
|
|
6748
7571
|
try {
|
|
@@ -6755,7 +7578,7 @@ async function writeAtomically(target, contents) {
|
|
|
6755
7578
|
}
|
|
6756
7579
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
6757
7580
|
const deadline = Date.now() + timeoutMs;
|
|
6758
|
-
await import_node_fs23.promises.mkdir(
|
|
7581
|
+
await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(lockPath), { recursive: true });
|
|
6759
7582
|
let probedHolder = false;
|
|
6760
7583
|
while (true) {
|
|
6761
7584
|
try {
|
|
@@ -6808,10 +7631,10 @@ async function readRegistry() {
|
|
|
6808
7631
|
throw err;
|
|
6809
7632
|
}
|
|
6810
7633
|
const parsed = JSON.parse(raw);
|
|
6811
|
-
return
|
|
7634
|
+
return import_types27.RegistryFileSchema.parse(parsed);
|
|
6812
7635
|
}
|
|
6813
7636
|
async function writeRegistry(reg) {
|
|
6814
|
-
const validated =
|
|
7637
|
+
const validated = import_types27.RegistryFileSchema.parse(reg);
|
|
6815
7638
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
6816
7639
|
}
|
|
6817
7640
|
async function getProject(name) {
|
|
@@ -7102,11 +7925,11 @@ function registerRoutes(scope, ctx) {
|
|
|
7102
7925
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7103
7926
|
const parsed = [];
|
|
7104
7927
|
for (const c of candidates) {
|
|
7105
|
-
const r =
|
|
7928
|
+
const r = import_types28.DivergenceTypeSchema.safeParse(c);
|
|
7106
7929
|
if (!r.success) {
|
|
7107
7930
|
return reply.code(400).send({
|
|
7108
7931
|
error: `unknown divergence type "${c}"`,
|
|
7109
|
-
allowed:
|
|
7932
|
+
allowed: import_types28.DivergenceTypeSchema.options
|
|
7110
7933
|
});
|
|
7111
7934
|
}
|
|
7112
7935
|
parsed.push(r.data);
|
|
@@ -7325,7 +8148,7 @@ function registerRoutes(scope, ctx) {
|
|
|
7325
8148
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
7326
8149
|
let violations = await log.readAll();
|
|
7327
8150
|
if (req2.query.severity) {
|
|
7328
|
-
const sev =
|
|
8151
|
+
const sev = import_types28.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
7329
8152
|
if (!sev.success) {
|
|
7330
8153
|
return reply.code(400).send({
|
|
7331
8154
|
error: "invalid severity",
|
|
@@ -7364,7 +8187,7 @@ function registerRoutes(scope, ctx) {
|
|
|
7364
8187
|
scope.post("/policies/check", async (req2, reply) => {
|
|
7365
8188
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
7366
8189
|
if (!proj) return;
|
|
7367
|
-
const parsed =
|
|
8190
|
+
const parsed = import_types28.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
7368
8191
|
if (!parsed.success) {
|
|
7369
8192
|
return reply.code(400).send({
|
|
7370
8193
|
error: "invalid /policies/check body",
|
|
@@ -7627,7 +8450,7 @@ init_auth();
|
|
|
7627
8450
|
// src/unrouted.ts
|
|
7628
8451
|
init_cjs_shims();
|
|
7629
8452
|
var import_node_fs24 = require("fs");
|
|
7630
|
-
var
|
|
8453
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
7631
8454
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
7632
8455
|
return {
|
|
7633
8456
|
timestamp: now.toISOString(),
|
|
@@ -7637,34 +8460,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
7637
8460
|
};
|
|
7638
8461
|
}
|
|
7639
8462
|
async function appendUnroutedSpan(neatHome3, record) {
|
|
7640
|
-
const target =
|
|
8463
|
+
const target = import_node_path43.default.join(neatHome3, "errors.ndjson");
|
|
7641
8464
|
await import_node_fs24.promises.mkdir(neatHome3, { recursive: true });
|
|
7642
8465
|
await import_node_fs24.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
7643
8466
|
}
|
|
7644
8467
|
function unroutedErrorsPath(neatHome3) {
|
|
7645
|
-
return
|
|
8468
|
+
return import_node_path43.default.join(neatHome3, "errors.ndjson");
|
|
7646
8469
|
}
|
|
7647
8470
|
|
|
7648
8471
|
// src/daemon.ts
|
|
7649
|
-
var
|
|
8472
|
+
var import_types29 = require("@neat.is/types");
|
|
7650
8473
|
function daemonJsonPath(scanPath) {
|
|
7651
|
-
return
|
|
8474
|
+
return import_node_path44.default.join(scanPath, "neat-out", "daemon.json");
|
|
7652
8475
|
}
|
|
7653
8476
|
function daemonsDiscoveryDir(home) {
|
|
7654
8477
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
7655
|
-
return
|
|
8478
|
+
return import_node_path44.default.join(base, "daemons");
|
|
7656
8479
|
}
|
|
7657
8480
|
function daemonDiscoveryPath(project, home) {
|
|
7658
|
-
return
|
|
8481
|
+
return import_node_path44.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
7659
8482
|
}
|
|
7660
8483
|
function sanitizeDiscoveryName(project) {
|
|
7661
8484
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
7662
8485
|
}
|
|
7663
8486
|
function neatHomeFromEnv() {
|
|
7664
8487
|
const env = process.env.NEAT_HOME;
|
|
7665
|
-
if (env && env.length > 0) return
|
|
8488
|
+
if (env && env.length > 0) return import_node_path44.default.resolve(env);
|
|
7666
8489
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
7667
|
-
return
|
|
8490
|
+
return import_node_path44.default.join(home, ".neat");
|
|
7668
8491
|
}
|
|
7669
8492
|
function resolveNeatVersion() {
|
|
7670
8493
|
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
@@ -7719,17 +8542,21 @@ function teardownSlot(slot) {
|
|
|
7719
8542
|
slot.stopPersist();
|
|
7720
8543
|
} catch {
|
|
7721
8544
|
}
|
|
8545
|
+
try {
|
|
8546
|
+
slot.stopStaleness();
|
|
8547
|
+
} catch {
|
|
8548
|
+
}
|
|
7722
8549
|
try {
|
|
7723
8550
|
slot.detachEvents();
|
|
7724
8551
|
} catch {
|
|
7725
8552
|
}
|
|
7726
8553
|
}
|
|
7727
8554
|
function neatHomeFor(opts) {
|
|
7728
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
8555
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path44.default.resolve(opts.neatHome);
|
|
7729
8556
|
const env = process.env.NEAT_HOME;
|
|
7730
|
-
if (env && env.length > 0) return
|
|
8557
|
+
if (env && env.length > 0) return import_node_path44.default.resolve(env);
|
|
7731
8558
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
7732
|
-
return
|
|
8559
|
+
return import_node_path44.default.join(home, ".neat");
|
|
7733
8560
|
}
|
|
7734
8561
|
function routeSpanToProject(serviceName, projects) {
|
|
7735
8562
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -7773,11 +8600,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
7773
8600
|
if (!serviceName) return true;
|
|
7774
8601
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
7775
8602
|
return graph.someNode(
|
|
7776
|
-
(_id, attrs) => attrs.type ===
|
|
8603
|
+
(_id, attrs) => attrs.type === import_types29.NodeType.ServiceNode && attrs.name === serviceName
|
|
7777
8604
|
);
|
|
7778
8605
|
}
|
|
7779
8606
|
async function bootstrapProject(entry2) {
|
|
7780
|
-
const paths = pathsForProject(entry2.name,
|
|
8607
|
+
const paths = pathsForProject(entry2.name, import_node_path44.default.join(entry2.path, "neat-out"));
|
|
7781
8608
|
try {
|
|
7782
8609
|
const stat = await import_node_fs25.promises.stat(entry2.path);
|
|
7783
8610
|
if (!stat.isDirectory()) {
|
|
@@ -7795,6 +8622,8 @@ async function bootstrapProject(entry2) {
|
|
|
7795
8622
|
paths,
|
|
7796
8623
|
stopPersist: () => {
|
|
7797
8624
|
},
|
|
8625
|
+
stopStaleness: () => {
|
|
8626
|
+
},
|
|
7798
8627
|
detachEvents: () => {
|
|
7799
8628
|
},
|
|
7800
8629
|
status: "broken",
|
|
@@ -7809,6 +8638,10 @@ async function bootstrapProject(entry2) {
|
|
|
7809
8638
|
try {
|
|
7810
8639
|
await extractFromDirectory(graph, entry2.path);
|
|
7811
8640
|
const stopPersist = startPersistLoop(graph, outPath, { exitOnSignal: false });
|
|
8641
|
+
const stopStaleness = startStalenessLoop(graph, {
|
|
8642
|
+
staleEventsPath: paths.staleEventsPath,
|
|
8643
|
+
project: entry2.name
|
|
8644
|
+
});
|
|
7812
8645
|
await touchLastSeen(entry2.name).catch(() => {
|
|
7813
8646
|
});
|
|
7814
8647
|
return {
|
|
@@ -7817,6 +8650,7 @@ async function bootstrapProject(entry2) {
|
|
|
7817
8650
|
outPath,
|
|
7818
8651
|
paths,
|
|
7819
8652
|
stopPersist,
|
|
8653
|
+
stopStaleness,
|
|
7820
8654
|
detachEvents,
|
|
7821
8655
|
status: "active"
|
|
7822
8656
|
};
|
|
@@ -7873,7 +8707,7 @@ async function startDaemon(opts = {}) {
|
|
|
7873
8707
|
const projectArg = typeof opts.project === "string" && opts.project.length > 0 ? opts.project : process.env.NEAT_PROJECT && process.env.NEAT_PROJECT.length > 0 ? process.env.NEAT_PROJECT : null;
|
|
7874
8708
|
const projectPathArg = opts.projectPath && opts.projectPath.length > 0 ? opts.projectPath : process.env.NEAT_PROJECT_PATH && process.env.NEAT_PROJECT_PATH.length > 0 ? process.env.NEAT_PROJECT_PATH : null;
|
|
7875
8709
|
const singleProject = projectArg;
|
|
7876
|
-
const singleProjectPath = singleProject && projectPathArg ?
|
|
8710
|
+
const singleProjectPath = singleProject && projectPathArg ? import_node_path44.default.resolve(projectPathArg) : null;
|
|
7877
8711
|
if (singleProject && !singleProjectPath) {
|
|
7878
8712
|
throw new Error(
|
|
7879
8713
|
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
@@ -7888,7 +8722,7 @@ async function startDaemon(opts = {}) {
|
|
|
7888
8722
|
);
|
|
7889
8723
|
}
|
|
7890
8724
|
}
|
|
7891
|
-
const pidPath =
|
|
8725
|
+
const pidPath = import_node_path44.default.join(home, "neatd.pid");
|
|
7892
8726
|
await writeAtomically(pidPath, `${process.pid}
|
|
7893
8727
|
`);
|
|
7894
8728
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -8290,8 +9124,8 @@ async function startDaemon(opts = {}) {
|
|
|
8290
9124
|
let registryWatcher = null;
|
|
8291
9125
|
let reloadTimer = null;
|
|
8292
9126
|
if (!singleProject) try {
|
|
8293
|
-
const regDir =
|
|
8294
|
-
const regBase =
|
|
9127
|
+
const regDir = import_node_path44.default.dirname(regPath);
|
|
9128
|
+
const regBase = import_node_path44.default.basename(regPath);
|
|
8295
9129
|
registryWatcher = (0, import_node_fs25.watch)(regDir, (_eventType, filename) => {
|
|
8296
9130
|
if (filename !== null && filename !== regBase) return;
|
|
8297
9131
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
@@ -8366,7 +9200,7 @@ init_cjs_shims();
|
|
|
8366
9200
|
var import_node_child_process2 = require("child_process");
|
|
8367
9201
|
var import_node_fs26 = require("fs");
|
|
8368
9202
|
var import_node_net = __toESM(require("net"), 1);
|
|
8369
|
-
var
|
|
9203
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
8370
9204
|
var DEFAULT_WEB_PORT = 6328;
|
|
8371
9205
|
var DEFAULT_REST_PORT = 8080;
|
|
8372
9206
|
function asValidPort(value) {
|
|
@@ -8375,11 +9209,11 @@ function asValidPort(value) {
|
|
|
8375
9209
|
}
|
|
8376
9210
|
function projectRoot() {
|
|
8377
9211
|
const fromEnv = process.env.NEAT_SCAN_PATH;
|
|
8378
|
-
return
|
|
9212
|
+
return import_node_path45.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
|
|
8379
9213
|
}
|
|
8380
9214
|
async function readDaemonPorts(root) {
|
|
8381
9215
|
try {
|
|
8382
|
-
const raw = await import_node_fs26.promises.readFile(
|
|
9216
|
+
const raw = await import_node_fs26.promises.readFile(import_node_path45.default.join(root, "neat-out", "daemon.json"), "utf8");
|
|
8383
9217
|
const parsed = JSON.parse(raw);
|
|
8384
9218
|
const ports = parsed?.ports ?? {};
|
|
8385
9219
|
return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
|
|
@@ -8424,10 +9258,10 @@ function resolveWebPackageDir() {
|
|
|
8424
9258
|
eval("require")
|
|
8425
9259
|
);
|
|
8426
9260
|
const pkgJsonPath = req.resolve("@neat.is/web/package.json");
|
|
8427
|
-
return
|
|
9261
|
+
return import_node_path45.default.dirname(pkgJsonPath);
|
|
8428
9262
|
}
|
|
8429
9263
|
function resolveStandaloneServerEntry(webDir) {
|
|
8430
|
-
return
|
|
9264
|
+
return import_node_path45.default.join(webDir, ".next/standalone/packages/web/server.js");
|
|
8431
9265
|
}
|
|
8432
9266
|
async function pickInternalPort() {
|
|
8433
9267
|
return new Promise((resolve, reject) => {
|
|
@@ -8480,7 +9314,7 @@ async function spawnWebUI(restPort, opts = {}) {
|
|
|
8480
9314
|
NEAT_API_URL: apiUrl
|
|
8481
9315
|
};
|
|
8482
9316
|
child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
|
|
8483
|
-
cwd:
|
|
9317
|
+
cwd: import_node_path45.default.dirname(serverEntry),
|
|
8484
9318
|
env,
|
|
8485
9319
|
stdio: ["ignore", "inherit", "inherit"],
|
|
8486
9320
|
detached: false
|
|
@@ -8656,14 +9490,14 @@ function localVersion() {
|
|
|
8656
9490
|
}
|
|
8657
9491
|
function neatHome2() {
|
|
8658
9492
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
8659
|
-
return
|
|
9493
|
+
return import_node_path46.default.resolve(process.env.NEAT_HOME);
|
|
8660
9494
|
}
|
|
8661
9495
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
8662
|
-
return
|
|
9496
|
+
return import_node_path46.default.join(home, ".neat");
|
|
8663
9497
|
}
|
|
8664
9498
|
async function readPid() {
|
|
8665
9499
|
try {
|
|
8666
|
-
const raw = await import_node_fs27.promises.readFile(
|
|
9500
|
+
const raw = await import_node_fs27.promises.readFile(import_node_path46.default.join(neatHome2(), "neatd.pid"), "utf8");
|
|
8667
9501
|
const n = Number.parseInt(raw.trim(), 10);
|
|
8668
9502
|
return Number.isFinite(n) ? n : null;
|
|
8669
9503
|
} catch {
|