@neat.is/core 0.4.26-dev.20260702 → 0.4.26
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-A3322JYS.js} +57 -3
- package/dist/chunk-A3322JYS.js.map +1 -0
- package/dist/{chunk-XOOCA5T7.js → chunk-BIY46Q6U.js} +25 -1
- package/dist/chunk-BIY46Q6U.js.map +1 -0
- package/dist/{chunk-O25KZNZK.js → chunk-QM6BMPVJ.js} +1200 -280
- package/dist/chunk-QM6BMPVJ.js.map +1 -0
- package/dist/{chunk-GAFTW2OX.js → chunk-UV5WSM7M.js} +2 -2
- package/dist/{chunk-IABNGQT2.js → chunk-XV4D7A3Z.js} +117 -6
- package/dist/chunk-XV4D7A3Z.js.map +1 -0
- package/dist/cli.cjs +1565 -546
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +49 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1382 -326
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -1
- package/dist/index.d.ts +102 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +1543 -395
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-QYHUJ4OY.js → otel-grpc-ET5Z6KI6.js} +3 -3
- package/dist/server.cjs +1206 -235
- 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-UV5WSM7M.js.map} +0 -0
- /package/dist/{otel-grpc-QYHUJ4OY.js.map → otel-grpc-ET5Z6KI6.js.map} +0 -0
package/dist/cli.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", (req, reply, done) => {
|
|
61
|
-
const
|
|
61
|
+
const path56 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
62
62
|
for (const suffix of suffixes) {
|
|
63
|
-
if (
|
|
63
|
+
if (path56 === suffix || path56.endsWith(suffix)) {
|
|
64
64
|
done();
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
@@ -71,11 +71,13 @@ function mountBearerAuth(app, opts) {
|
|
|
71
71
|
}
|
|
72
72
|
const header = req.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(req) {
|
|
|
188
190
|
};
|
|
189
191
|
}
|
|
190
192
|
function resolveProtoRoot() {
|
|
191
|
-
const here =
|
|
192
|
-
return
|
|
193
|
+
const here = import_node_path44.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
194
|
+
return import_node_path44.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_url2,
|
|
262
|
+
var import_node_url2, import_node_path44, 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_url2 = require("url");
|
|
266
|
-
|
|
268
|
+
import_node_path44 = __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,36 @@ 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
|
+
}
|
|
348
|
+
function hasWebsocketUpgradeHeader(attrs) {
|
|
349
|
+
const v = attrs["http.request.header.upgrade"];
|
|
350
|
+
const matches = (s) => typeof s === "string" && s.trim().toLowerCase() === "websocket";
|
|
351
|
+
if (Array.isArray(v)) return v.some(matches);
|
|
352
|
+
return matches(v);
|
|
353
|
+
}
|
|
354
|
+
function websocketChannelPathOf(attrs) {
|
|
355
|
+
const route = attrs["http.route"];
|
|
356
|
+
if (typeof route === "string" && route.length > 0) return route;
|
|
357
|
+
for (const key of ["url.path", "http.target"]) {
|
|
358
|
+
const v = attrs[key];
|
|
359
|
+
if (typeof v === "string" && v.length > 0) {
|
|
360
|
+
const q = v.indexOf("?");
|
|
361
|
+
const path56 = q === -1 ? v : v.slice(0, q);
|
|
362
|
+
if (path56.length > 0) return path56;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return void 0;
|
|
366
|
+
}
|
|
367
|
+
function websocketChannelOf(attrs) {
|
|
368
|
+
if (!hasWebsocketUpgradeHeader(attrs)) return void 0;
|
|
369
|
+
return websocketChannelPathOf(attrs);
|
|
370
|
+
}
|
|
339
371
|
function parseOtlpRequest(body) {
|
|
340
372
|
const out = [];
|
|
341
373
|
for (const rs of body.resourceSpans ?? []) {
|
|
@@ -362,6 +394,14 @@ function parseOtlpRequest(body) {
|
|
|
362
394
|
attributes: attrs,
|
|
363
395
|
dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
|
|
364
396
|
dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
|
|
397
|
+
messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
|
|
398
|
+
messagingDestination: messagingDestinationOf(attrs),
|
|
399
|
+
graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
|
|
400
|
+
graphqlOperationType: typeof attrs["graphql.operation.type"] === "string" && attrs["graphql.operation.type"].length > 0 ? attrs["graphql.operation.type"] : void 0,
|
|
401
|
+
rpcSystem: typeof attrs["rpc.system"] === "string" && attrs["rpc.system"].length > 0 ? attrs["rpc.system"] : void 0,
|
|
402
|
+
rpcService: typeof attrs["rpc.service"] === "string" && attrs["rpc.service"].length > 0 ? attrs["rpc.service"] : void 0,
|
|
403
|
+
rpcMethod: typeof attrs["rpc.method"] === "string" && attrs["rpc.method"].length > 0 ? attrs["rpc.method"] : void 0,
|
|
404
|
+
websocketChannel: websocketChannelOf(attrs),
|
|
365
405
|
statusCode: span.status?.code,
|
|
366
406
|
errorMessage: span.status?.message,
|
|
367
407
|
exception: extractExceptionFromEvents(span.events)
|
|
@@ -373,10 +413,10 @@ function parseOtlpRequest(body) {
|
|
|
373
413
|
return out;
|
|
374
414
|
}
|
|
375
415
|
function loadProtoRoot() {
|
|
376
|
-
const here =
|
|
377
|
-
const protoRoot =
|
|
416
|
+
const here = import_node_path45.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
|
|
417
|
+
const protoRoot = import_node_path45.default.resolve(here, "..", "proto");
|
|
378
418
|
const root = new import_protobufjs.default.Root();
|
|
379
|
-
root.resolvePath = (_origin, target) =>
|
|
419
|
+
root.resolvePath = (_origin, target) => import_node_path45.default.resolve(protoRoot, target);
|
|
380
420
|
root.loadSync(
|
|
381
421
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
382
422
|
{ keepCase: true }
|
|
@@ -421,7 +461,21 @@ async function buildOtelReceiver(opts) {
|
|
|
421
461
|
logger: false,
|
|
422
462
|
bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024
|
|
423
463
|
});
|
|
424
|
-
|
|
464
|
+
const REJECT_WARN_INTERVAL_MS = 6e4;
|
|
465
|
+
let lastRejectWarnAt = 0;
|
|
466
|
+
const warnRejectedOtlp = () => {
|
|
467
|
+
const now = Date.now();
|
|
468
|
+
if (now - lastRejectWarnAt < REJECT_WARN_INTERVAL_MS) return;
|
|
469
|
+
lastRejectWarnAt = now;
|
|
470
|
+
console.warn(
|
|
471
|
+
"[neatd] rejecting OTLP spans on /v1/traces \u2014 missing or invalid bearer token (set NEAT_OTEL_TOKEN on the instrumented app)"
|
|
472
|
+
);
|
|
473
|
+
};
|
|
474
|
+
mountBearerAuth(app, {
|
|
475
|
+
token: opts.authToken,
|
|
476
|
+
trustProxy: opts.trustProxy,
|
|
477
|
+
onReject: warnRejectedOtlp
|
|
478
|
+
});
|
|
425
479
|
const queue = [];
|
|
426
480
|
let draining = false;
|
|
427
481
|
let drainPromise = Promise.resolve();
|
|
@@ -590,12 +644,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
|
|
|
590
644
|
}
|
|
591
645
|
}
|
|
592
646
|
}
|
|
593
|
-
var
|
|
647
|
+
var import_node_path45, import_node_url3, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
|
|
594
648
|
var init_otel = __esm({
|
|
595
649
|
"src/otel.ts"() {
|
|
596
650
|
"use strict";
|
|
597
651
|
init_cjs_shims();
|
|
598
|
-
|
|
652
|
+
import_node_path45 = __toESM(require("path"), 1);
|
|
599
653
|
import_node_url3 = require("url");
|
|
600
654
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
601
655
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -632,8 +686,8 @@ __export(cli_exports, {
|
|
|
632
686
|
});
|
|
633
687
|
module.exports = __toCommonJS(cli_exports);
|
|
634
688
|
init_cjs_shims();
|
|
635
|
-
var
|
|
636
|
-
var
|
|
689
|
+
var import_node_path55 = __toESM(require("path"), 1);
|
|
690
|
+
var import_node_fs35 = require("fs");
|
|
637
691
|
|
|
638
692
|
// src/banner.ts
|
|
639
693
|
init_cjs_shims();
|
|
@@ -1198,19 +1252,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1198
1252
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1199
1253
|
let best = { path: [start], edges: [] };
|
|
1200
1254
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1201
|
-
function step(node,
|
|
1202
|
-
if (
|
|
1203
|
-
best = { path: [...
|
|
1255
|
+
function step(node, path56, edges) {
|
|
1256
|
+
if (path56.length > best.path.length) {
|
|
1257
|
+
best = { path: [...path56], edges: [...edges] };
|
|
1204
1258
|
}
|
|
1205
|
-
if (
|
|
1259
|
+
if (path56.length - 1 >= maxDepth) return;
|
|
1206
1260
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1207
1261
|
for (const [srcId, edge] of incoming) {
|
|
1208
1262
|
if (visited.has(srcId)) continue;
|
|
1209
1263
|
visited.add(srcId);
|
|
1210
|
-
|
|
1264
|
+
path56.push(srcId);
|
|
1211
1265
|
edges.push(edge);
|
|
1212
|
-
step(srcId,
|
|
1213
|
-
|
|
1266
|
+
step(srcId, path56, edges);
|
|
1267
|
+
path56.pop();
|
|
1214
1268
|
edges.pop();
|
|
1215
1269
|
visited.delete(srcId);
|
|
1216
1270
|
}
|
|
@@ -1218,14 +1272,14 @@ function longestIncomingWalk(graph, start, maxDepth) {
|
|
|
1218
1272
|
step(start, [start], []);
|
|
1219
1273
|
return best;
|
|
1220
1274
|
}
|
|
1221
|
-
function databaseRootCauseShape(graph, origin,
|
|
1275
|
+
function databaseRootCauseShape(graph, origin, walk3) {
|
|
1222
1276
|
const targetDb = origin;
|
|
1223
1277
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
1224
1278
|
if (candidatePairs.length === 0) return null;
|
|
1225
|
-
for (const id of
|
|
1279
|
+
for (const id of walk3.path) {
|
|
1226
1280
|
const owner = resolveOwningService(graph, id);
|
|
1227
1281
|
if (!owner) continue;
|
|
1228
|
-
const { id:
|
|
1282
|
+
const { id: serviceId4, svc } = owner;
|
|
1229
1283
|
const deps = svc.dependencies ?? {};
|
|
1230
1284
|
for (const pair of candidatePairs) {
|
|
1231
1285
|
const declared = deps[pair.driver];
|
|
@@ -1238,7 +1292,7 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1238
1292
|
);
|
|
1239
1293
|
if (!result.compatible) {
|
|
1240
1294
|
return {
|
|
1241
|
-
rootCauseNode:
|
|
1295
|
+
rootCauseNode: serviceId4,
|
|
1242
1296
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1243
1297
|
...result.minDriverVersion ? {
|
|
1244
1298
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1249,11 +1303,11 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1249
1303
|
}
|
|
1250
1304
|
return null;
|
|
1251
1305
|
}
|
|
1252
|
-
function serviceRootCauseShape(graph, _origin,
|
|
1253
|
-
for (const id of
|
|
1306
|
+
function serviceRootCauseShape(graph, _origin, walk3) {
|
|
1307
|
+
for (const id of walk3.path) {
|
|
1254
1308
|
const owner = resolveOwningService(graph, id);
|
|
1255
1309
|
if (!owner) continue;
|
|
1256
|
-
const { id:
|
|
1310
|
+
const { id: serviceId4, svc } = owner;
|
|
1257
1311
|
const deps = svc.dependencies ?? {};
|
|
1258
1312
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1259
1313
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1262,7 +1316,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1262
1316
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1263
1317
|
if (!result.compatible && result.reason) {
|
|
1264
1318
|
return {
|
|
1265
|
-
rootCauseNode:
|
|
1319
|
+
rootCauseNode: serviceId4,
|
|
1266
1320
|
rootCauseReason: result.reason,
|
|
1267
1321
|
...result.requiredNodeVersion ? {
|
|
1268
1322
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1277,7 +1331,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1277
1331
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1278
1332
|
if (!result.compatible && result.reason) {
|
|
1279
1333
|
return {
|
|
1280
|
-
rootCauseNode:
|
|
1334
|
+
rootCauseNode: serviceId4,
|
|
1281
1335
|
rootCauseReason: result.reason,
|
|
1282
1336
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1283
1337
|
};
|
|
@@ -1286,10 +1340,10 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1286
1340
|
}
|
|
1287
1341
|
return null;
|
|
1288
1342
|
}
|
|
1289
|
-
function fileRootCauseShape(graph, origin,
|
|
1343
|
+
function fileRootCauseShape(graph, origin, walk3) {
|
|
1290
1344
|
const owner = resolveOwningService(graph, origin.id);
|
|
1291
1345
|
if (!owner) return null;
|
|
1292
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
1346
|
+
return serviceRootCauseShape(graph, owner.svc, walk3);
|
|
1293
1347
|
}
|
|
1294
1348
|
var rootCauseShapes = {
|
|
1295
1349
|
[import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
@@ -1301,16 +1355,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
1301
1355
|
const origin = graph.getNodeAttributes(errorNodeId);
|
|
1302
1356
|
const shape = rootCauseShapes[origin.type];
|
|
1303
1357
|
if (shape) {
|
|
1304
|
-
const
|
|
1305
|
-
const match = shape(graph, origin,
|
|
1358
|
+
const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
|
|
1359
|
+
const match = shape(graph, origin, walk3);
|
|
1306
1360
|
if (match) {
|
|
1307
1361
|
const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
|
|
1308
1362
|
return import_types.RootCauseResultSchema.parse({
|
|
1309
1363
|
rootCauseNode: match.rootCauseNode,
|
|
1310
1364
|
rootCauseReason: reason,
|
|
1311
|
-
traversalPath:
|
|
1312
|
-
edgeProvenances:
|
|
1313
|
-
confidence: confidenceFromMix(
|
|
1365
|
+
traversalPath: walk3.path,
|
|
1366
|
+
edgeProvenances: walk3.edges.map((e) => e.provenance),
|
|
1367
|
+
confidence: confidenceFromMix(walk3.edges),
|
|
1314
1368
|
fixRecommendation: match.fixRecommendation
|
|
1315
1369
|
});
|
|
1316
1370
|
}
|
|
@@ -1368,9 +1422,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
1368
1422
|
function isFailingCallEdge(e) {
|
|
1369
1423
|
return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
1370
1424
|
}
|
|
1371
|
-
function callSourcesForService(graph,
|
|
1372
|
-
const ids = [
|
|
1373
|
-
for (const edgeId of graph.outboundEdges(
|
|
1425
|
+
function callSourcesForService(graph, serviceId4) {
|
|
1426
|
+
const ids = [serviceId4];
|
|
1427
|
+
for (const edgeId of graph.outboundEdges(serviceId4)) {
|
|
1374
1428
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1375
1429
|
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
1376
1430
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -1387,9 +1441,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
1387
1441
|
}
|
|
1388
1442
|
return id < curId;
|
|
1389
1443
|
}
|
|
1390
|
-
function dominantFailingCall(graph,
|
|
1444
|
+
function dominantFailingCall(graph, serviceId4, visited) {
|
|
1391
1445
|
let best = null;
|
|
1392
|
-
for (const src of callSourcesForService(graph,
|
|
1446
|
+
for (const src of callSourcesForService(graph, serviceId4)) {
|
|
1393
1447
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
1394
1448
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1395
1449
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -1404,26 +1458,26 @@ function dominantFailingCall(graph, serviceId3, visited) {
|
|
|
1404
1458
|
return best;
|
|
1405
1459
|
}
|
|
1406
1460
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1407
|
-
const
|
|
1461
|
+
const path56 = [originServiceId];
|
|
1408
1462
|
const edges = [];
|
|
1409
1463
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1410
1464
|
let current = originServiceId;
|
|
1411
1465
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1412
1466
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1413
1467
|
if (!hop) break;
|
|
1414
|
-
|
|
1468
|
+
path56.push(hop.nextService);
|
|
1415
1469
|
edges.push(hop.edge);
|
|
1416
1470
|
visited.add(hop.nextService);
|
|
1417
1471
|
current = hop.nextService;
|
|
1418
1472
|
}
|
|
1419
1473
|
if (edges.length === 0) return null;
|
|
1420
|
-
return { path:
|
|
1474
|
+
return { path: path56, edges, culprit: current };
|
|
1421
1475
|
}
|
|
1422
1476
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1423
1477
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1424
1478
|
if (!chain) return null;
|
|
1425
1479
|
const culprit = chain.culprit;
|
|
1426
|
-
const
|
|
1480
|
+
const path56 = [...chain.path];
|
|
1427
1481
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1428
1482
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1429
1483
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1431,14 +1485,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1431
1485
|
if (loc) {
|
|
1432
1486
|
let rootCauseNode = culprit;
|
|
1433
1487
|
if (loc.fileNode) {
|
|
1434
|
-
|
|
1488
|
+
path56.push(loc.fileNode);
|
|
1435
1489
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1436
1490
|
rootCauseNode = loc.fileNode;
|
|
1437
1491
|
}
|
|
1438
1492
|
return import_types.RootCauseResultSchema.parse({
|
|
1439
1493
|
rootCauseNode,
|
|
1440
1494
|
rootCauseReason: loc.rootCauseReason,
|
|
1441
|
-
traversalPath:
|
|
1495
|
+
traversalPath: path56,
|
|
1442
1496
|
edgeProvenances,
|
|
1443
1497
|
confidence,
|
|
1444
1498
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1450,7 +1504,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1450
1504
|
return import_types.RootCauseResultSchema.parse({
|
|
1451
1505
|
rootCauseNode: culprit,
|
|
1452
1506
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1453
|
-
traversalPath:
|
|
1507
|
+
traversalPath: path56,
|
|
1454
1508
|
edgeProvenances,
|
|
1455
1509
|
confidence,
|
|
1456
1510
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -2374,10 +2428,82 @@ var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
|
2374
2428
|
]);
|
|
2375
2429
|
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
2376
2430
|
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
2431
|
+
var WIRE_SPAN_KIND_CONSUMER = 5;
|
|
2377
2432
|
function spanMintsObservedEdge(kind) {
|
|
2378
2433
|
if (kind === void 0 || kind === 0) return true;
|
|
2379
2434
|
return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
|
|
2380
2435
|
}
|
|
2436
|
+
function spanMintsMessagingEdge(kind) {
|
|
2437
|
+
return kind === WIRE_SPAN_KIND_PRODUCER || kind === WIRE_SPAN_KIND_CONSUMER;
|
|
2438
|
+
}
|
|
2439
|
+
function spanServesGraphqlOperation(kind) {
|
|
2440
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2441
|
+
}
|
|
2442
|
+
function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
|
|
2443
|
+
const id = (0, import_types3.graphqlOperationId)(serviceName, operationType, operationName);
|
|
2444
|
+
if (graph.hasNode(id)) return id;
|
|
2445
|
+
const node = {
|
|
2446
|
+
id,
|
|
2447
|
+
type: import_types3.NodeType.GraphQLOperationNode,
|
|
2448
|
+
name: operationName,
|
|
2449
|
+
service: serviceName,
|
|
2450
|
+
operationType: operationType.toLowerCase(),
|
|
2451
|
+
operationName,
|
|
2452
|
+
discoveredVia: "otel"
|
|
2453
|
+
};
|
|
2454
|
+
graph.addNode(id, node);
|
|
2455
|
+
return id;
|
|
2456
|
+
}
|
|
2457
|
+
function spanServesGrpcMethod(kind) {
|
|
2458
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2459
|
+
}
|
|
2460
|
+
function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
|
|
2461
|
+
const id = (0, import_types3.grpcMethodId)(rpcService, rpcMethod);
|
|
2462
|
+
if (graph.hasNode(id)) return id;
|
|
2463
|
+
const node = {
|
|
2464
|
+
id,
|
|
2465
|
+
type: import_types3.NodeType.GrpcMethodNode,
|
|
2466
|
+
name: `${rpcService}/${rpcMethod}`,
|
|
2467
|
+
rpcService,
|
|
2468
|
+
rpcMethod,
|
|
2469
|
+
discoveredVia: "otel"
|
|
2470
|
+
};
|
|
2471
|
+
graph.addNode(id, node);
|
|
2472
|
+
return id;
|
|
2473
|
+
}
|
|
2474
|
+
function spanServesWebsocketChannel(kind) {
|
|
2475
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2476
|
+
}
|
|
2477
|
+
function ensureWebsocketChannelNode(graph, serviceName, channel) {
|
|
2478
|
+
const id = (0, import_types3.websocketChannelId)(serviceName, channel);
|
|
2479
|
+
if (graph.hasNode(id)) return id;
|
|
2480
|
+
const node = {
|
|
2481
|
+
id,
|
|
2482
|
+
type: import_types3.NodeType.WebSocketChannelNode,
|
|
2483
|
+
name: channel,
|
|
2484
|
+
service: serviceName,
|
|
2485
|
+
channel,
|
|
2486
|
+
discoveredVia: "otel"
|
|
2487
|
+
};
|
|
2488
|
+
graph.addNode(id, node);
|
|
2489
|
+
return id;
|
|
2490
|
+
}
|
|
2491
|
+
function messagingDestinationKind(system) {
|
|
2492
|
+
return `${system}-topic`;
|
|
2493
|
+
}
|
|
2494
|
+
function ensureMessagingDestinationNode(graph, system, destination) {
|
|
2495
|
+
const id = (0, import_types3.infraId)(messagingDestinationKind(system), destination);
|
|
2496
|
+
if (graph.hasNode(id)) return id;
|
|
2497
|
+
const node = {
|
|
2498
|
+
id,
|
|
2499
|
+
type: import_types3.NodeType.InfraNode,
|
|
2500
|
+
name: destination,
|
|
2501
|
+
provider: "self",
|
|
2502
|
+
kind: messagingDestinationKind(system)
|
|
2503
|
+
};
|
|
2504
|
+
graph.addNode(id, node);
|
|
2505
|
+
return id;
|
|
2506
|
+
}
|
|
2381
2507
|
var PARENT_SPAN_CACHE_SIZE = 1e4;
|
|
2382
2508
|
var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2383
2509
|
var parentSpanCache = /* @__PURE__ */ new Map();
|
|
@@ -2471,6 +2597,21 @@ function ensureDatabaseNode(graph, host, engine) {
|
|
|
2471
2597
|
graph.addNode(id, node);
|
|
2472
2598
|
return id;
|
|
2473
2599
|
}
|
|
2600
|
+
function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
|
|
2601
|
+
const id = (0, import_types3.localDatabaseId)(serviceName, name);
|
|
2602
|
+
if (graph.hasNode(id)) return id;
|
|
2603
|
+
const node = {
|
|
2604
|
+
id,
|
|
2605
|
+
type: import_types3.NodeType.DatabaseNode,
|
|
2606
|
+
name,
|
|
2607
|
+
engine,
|
|
2608
|
+
engineVersion: "unknown",
|
|
2609
|
+
compatibleDrivers: [],
|
|
2610
|
+
discoveredVia: "otel"
|
|
2611
|
+
};
|
|
2612
|
+
graph.addNode(id, node);
|
|
2613
|
+
return id;
|
|
2614
|
+
}
|
|
2474
2615
|
function ensureFrontierNode(graph, host, ts) {
|
|
2475
2616
|
const id = frontierIdFor(host);
|
|
2476
2617
|
if (graph.hasNode(id)) {
|
|
@@ -2722,10 +2863,21 @@ async function handleSpan(ctx, span) {
|
|
|
2722
2863
|
let affectedNode = sourceId;
|
|
2723
2864
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2724
2865
|
if (span.dbSystem) {
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2866
|
+
if (mintsFromCallerSide) {
|
|
2867
|
+
const host = pickAddress(span);
|
|
2868
|
+
let targetId;
|
|
2869
|
+
if (host) {
|
|
2870
|
+
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
2871
|
+
targetId = (0, import_types3.databaseId)(host);
|
|
2872
|
+
} else {
|
|
2873
|
+
const localName = span.dbName ?? span.dbSystem;
|
|
2874
|
+
targetId = ensureLocalDatabaseNode(
|
|
2875
|
+
ctx.graph,
|
|
2876
|
+
span.service,
|
|
2877
|
+
localName,
|
|
2878
|
+
span.dbSystem
|
|
2879
|
+
);
|
|
2880
|
+
}
|
|
2729
2881
|
const result = upsertObservedEdge(
|
|
2730
2882
|
ctx.graph,
|
|
2731
2883
|
import_types3.EdgeType.CONNECTS_TO,
|
|
@@ -2737,6 +2889,68 @@ async function handleSpan(ctx, span) {
|
|
|
2737
2889
|
);
|
|
2738
2890
|
if (result) affectedNode = targetId;
|
|
2739
2891
|
}
|
|
2892
|
+
} else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
|
|
2893
|
+
const targetId = ensureMessagingDestinationNode(
|
|
2894
|
+
ctx.graph,
|
|
2895
|
+
span.messagingSystem,
|
|
2896
|
+
span.messagingDestination
|
|
2897
|
+
);
|
|
2898
|
+
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types3.EdgeType.CONSUMES_FROM : import_types3.EdgeType.PUBLISHES_TO;
|
|
2899
|
+
const result = upsertObservedEdge(
|
|
2900
|
+
ctx.graph,
|
|
2901
|
+
edgeType,
|
|
2902
|
+
observedSource(),
|
|
2903
|
+
targetId,
|
|
2904
|
+
ts,
|
|
2905
|
+
isError,
|
|
2906
|
+
callSiteEvidence
|
|
2907
|
+
);
|
|
2908
|
+
if (result) affectedNode = targetId;
|
|
2909
|
+
} else if (span.graphqlOperationName && span.graphqlOperationType && spanServesGraphqlOperation(span.kind)) {
|
|
2910
|
+
const targetId = ensureGraphqlOperationNode(
|
|
2911
|
+
ctx.graph,
|
|
2912
|
+
span.service,
|
|
2913
|
+
span.graphqlOperationType,
|
|
2914
|
+
span.graphqlOperationName
|
|
2915
|
+
);
|
|
2916
|
+
const result = upsertObservedEdge(
|
|
2917
|
+
ctx.graph,
|
|
2918
|
+
import_types3.EdgeType.CONTAINS,
|
|
2919
|
+
observedSource(),
|
|
2920
|
+
targetId,
|
|
2921
|
+
ts,
|
|
2922
|
+
isError,
|
|
2923
|
+
callSiteEvidence
|
|
2924
|
+
);
|
|
2925
|
+
if (result) affectedNode = targetId;
|
|
2926
|
+
} else if (span.rpcSystem === "grpc" && span.rpcService && span.rpcMethod && spanServesGrpcMethod(span.kind)) {
|
|
2927
|
+
const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
|
|
2928
|
+
const result = upsertObservedEdge(
|
|
2929
|
+
ctx.graph,
|
|
2930
|
+
import_types3.EdgeType.CONTAINS,
|
|
2931
|
+
observedSource(),
|
|
2932
|
+
targetId,
|
|
2933
|
+
ts,
|
|
2934
|
+
isError,
|
|
2935
|
+
callSiteEvidence
|
|
2936
|
+
);
|
|
2937
|
+
if (result) affectedNode = targetId;
|
|
2938
|
+
} else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {
|
|
2939
|
+
const targetId = ensureWebsocketChannelNode(
|
|
2940
|
+
ctx.graph,
|
|
2941
|
+
span.service,
|
|
2942
|
+
span.websocketChannel
|
|
2943
|
+
);
|
|
2944
|
+
const result = upsertObservedEdge(
|
|
2945
|
+
ctx.graph,
|
|
2946
|
+
import_types3.EdgeType.CONNECTS_TO,
|
|
2947
|
+
observedSource(),
|
|
2948
|
+
targetId,
|
|
2949
|
+
ts,
|
|
2950
|
+
isError,
|
|
2951
|
+
callSiteEvidence
|
|
2952
|
+
);
|
|
2953
|
+
if (result) affectedNode = targetId;
|
|
2740
2954
|
} else {
|
|
2741
2955
|
const host = pickAddress(span);
|
|
2742
2956
|
let resolvedViaAddress = false;
|
|
@@ -2846,29 +3060,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
2846
3060
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
2847
3061
|
});
|
|
2848
3062
|
let promoted = 0;
|
|
2849
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
3063
|
+
for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
|
|
2850
3064
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
2851
3065
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
2852
3066
|
if (!gate.allowed) {
|
|
2853
3067
|
continue;
|
|
2854
3068
|
}
|
|
2855
3069
|
}
|
|
2856
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
3070
|
+
rewireFrontierEdges(graph, frontierId2, serviceId4);
|
|
2857
3071
|
graph.dropNode(frontierId2);
|
|
2858
3072
|
promoted++;
|
|
2859
3073
|
}
|
|
2860
3074
|
return promoted;
|
|
2861
3075
|
}
|
|
2862
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
3076
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId4) {
|
|
2863
3077
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
2864
3078
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
2865
3079
|
for (const edgeId of inbound) {
|
|
2866
3080
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
2867
|
-
rebuildEdge(graph, edge, edge.source,
|
|
3081
|
+
rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
|
|
2868
3082
|
}
|
|
2869
3083
|
for (const edgeId of outbound) {
|
|
2870
3084
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
2871
|
-
rebuildEdge(graph, edge,
|
|
3085
|
+
rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
|
|
2872
3086
|
}
|
|
2873
3087
|
}
|
|
2874
3088
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -3642,9 +3856,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
3642
3856
|
"StatefulSet",
|
|
3643
3857
|
"DaemonSet"
|
|
3644
3858
|
]);
|
|
3645
|
-
function addAliases(graph,
|
|
3646
|
-
if (!graph.hasNode(
|
|
3647
|
-
const node = graph.getNodeAttributes(
|
|
3859
|
+
function addAliases(graph, serviceId4, candidates) {
|
|
3860
|
+
if (!graph.hasNode(serviceId4)) return;
|
|
3861
|
+
const node = graph.getNodeAttributes(serviceId4);
|
|
3648
3862
|
if (node.type !== import_types6.NodeType.ServiceNode) return;
|
|
3649
3863
|
const set = new Set(node.aliases ?? []);
|
|
3650
3864
|
for (const c of candidates) {
|
|
@@ -3654,7 +3868,7 @@ function addAliases(graph, serviceId3, candidates) {
|
|
|
3654
3868
|
}
|
|
3655
3869
|
if (set.size === 0) return;
|
|
3656
3870
|
const updated = { ...node, aliases: [...set].sort() };
|
|
3657
|
-
graph.replaceNodeAttributes(
|
|
3871
|
+
graph.replaceNodeAttributes(serviceId4, updated);
|
|
3658
3872
|
}
|
|
3659
3873
|
function indexServicesByName(services) {
|
|
3660
3874
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -3687,12 +3901,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
3687
3901
|
}
|
|
3688
3902
|
if (!compose?.services) return;
|
|
3689
3903
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3690
|
-
const
|
|
3691
|
-
if (!
|
|
3904
|
+
const serviceId4 = serviceIndex.get(composeName);
|
|
3905
|
+
if (!serviceId4) continue;
|
|
3692
3906
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
3693
3907
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
3694
3908
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
3695
|
-
addAliases(graph,
|
|
3909
|
+
addAliases(graph, serviceId4, aliases);
|
|
3696
3910
|
}
|
|
3697
3911
|
}
|
|
3698
3912
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -3805,16 +4019,28 @@ init_cjs_shims();
|
|
|
3805
4019
|
var import_node_fs11 = require("fs");
|
|
3806
4020
|
var import_node_path11 = __toESM(require("path"), 1);
|
|
3807
4021
|
var import_types7 = require("@neat.is/types");
|
|
4022
|
+
function buildServiceHostIndex(services) {
|
|
4023
|
+
const knownHosts = /* @__PURE__ */ new Set();
|
|
4024
|
+
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
4025
|
+
for (const service of services) {
|
|
4026
|
+
const base = import_node_path11.default.basename(service.dir);
|
|
4027
|
+
knownHosts.add(base);
|
|
4028
|
+
knownHosts.add(service.pkg.name);
|
|
4029
|
+
hostToNodeId.set(base, service.node.id);
|
|
4030
|
+
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
4031
|
+
}
|
|
4032
|
+
return { knownHosts, hostToNodeId };
|
|
4033
|
+
}
|
|
3808
4034
|
async function walkSourceFiles(dir) {
|
|
3809
4035
|
const out = [];
|
|
3810
|
-
async function
|
|
4036
|
+
async function walk3(current) {
|
|
3811
4037
|
const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3812
4038
|
for (const entry2 of entries) {
|
|
3813
4039
|
const full = import_node_path11.default.join(current, entry2.name);
|
|
3814
4040
|
if (entry2.isDirectory()) {
|
|
3815
4041
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3816
4042
|
if (await isPythonVenvDir(full)) continue;
|
|
3817
|
-
await
|
|
4043
|
+
await walk3(full);
|
|
3818
4044
|
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path11.default.extname(entry2.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
|
|
3819
4045
|
// would attribute our instrumentation imports to the user's service.
|
|
3820
4046
|
!isNeatAuthoredSourceFile(entry2.name)) {
|
|
@@ -3822,7 +4048,7 @@ async function walkSourceFiles(dir) {
|
|
|
3822
4048
|
}
|
|
3823
4049
|
}
|
|
3824
4050
|
}
|
|
3825
|
-
await
|
|
4051
|
+
await walk3(dir);
|
|
3826
4052
|
return out;
|
|
3827
4053
|
}
|
|
3828
4054
|
async function loadSourceFiles(dir) {
|
|
@@ -4916,20 +5142,20 @@ var import_node_path22 = __toESM(require("path"), 1);
|
|
|
4916
5142
|
var import_types10 = require("@neat.is/types");
|
|
4917
5143
|
async function walkConfigFiles(dir) {
|
|
4918
5144
|
const out = [];
|
|
4919
|
-
async function
|
|
5145
|
+
async function walk3(current) {
|
|
4920
5146
|
const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true });
|
|
4921
5147
|
for (const entry2 of entries) {
|
|
4922
5148
|
const full = import_node_path22.default.join(current, entry2.name);
|
|
4923
5149
|
if (entry2.isDirectory()) {
|
|
4924
5150
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
4925
5151
|
if (await isPythonVenvDir(full)) continue;
|
|
4926
|
-
await
|
|
5152
|
+
await walk3(full);
|
|
4927
5153
|
} else if (entry2.isFile() && isConfigFile(entry2.name).match) {
|
|
4928
5154
|
out.push(full);
|
|
4929
5155
|
}
|
|
4930
5156
|
}
|
|
4931
5157
|
}
|
|
4932
|
-
await
|
|
5158
|
+
await walk3(dir);
|
|
4933
5159
|
return out;
|
|
4934
5160
|
}
|
|
4935
5161
|
async function addConfigNodes(graph, services, scanPath) {
|
|
@@ -4977,17 +5203,468 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
4977
5203
|
return { nodesAdded, edgesAdded };
|
|
4978
5204
|
}
|
|
4979
5205
|
|
|
5206
|
+
// src/extract/routes.ts
|
|
5207
|
+
init_cjs_shims();
|
|
5208
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
5209
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
5210
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5211
|
+
var import_types11 = require("@neat.is/types");
|
|
5212
|
+
var PARSE_CHUNK2 = 16384;
|
|
5213
|
+
function parseSource2(parser, source) {
|
|
5214
|
+
return parser.parse(
|
|
5215
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
5216
|
+
);
|
|
5217
|
+
}
|
|
5218
|
+
function makeJsParser2() {
|
|
5219
|
+
const p = new import_tree_sitter2.default();
|
|
5220
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
5221
|
+
return p;
|
|
5222
|
+
}
|
|
5223
|
+
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
5224
|
+
"get",
|
|
5225
|
+
"post",
|
|
5226
|
+
"put",
|
|
5227
|
+
"patch",
|
|
5228
|
+
"delete",
|
|
5229
|
+
"options",
|
|
5230
|
+
"head",
|
|
5231
|
+
"all"
|
|
5232
|
+
]);
|
|
5233
|
+
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
5234
|
+
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5235
|
+
function canonicalizeTemplate(raw) {
|
|
5236
|
+
let p = raw.split("?")[0].split("#")[0];
|
|
5237
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
5238
|
+
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
5239
|
+
return p;
|
|
5240
|
+
}
|
|
5241
|
+
function isDynamicSegment(seg) {
|
|
5242
|
+
if (seg.length === 0) return false;
|
|
5243
|
+
if (seg.includes(":")) return true;
|
|
5244
|
+
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
5245
|
+
if (/^\d+$/.test(seg)) return true;
|
|
5246
|
+
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;
|
|
5247
|
+
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
5248
|
+
return false;
|
|
5249
|
+
}
|
|
5250
|
+
function normalizePathTemplate(raw) {
|
|
5251
|
+
const canonical = canonicalizeTemplate(raw);
|
|
5252
|
+
const segments = canonical.split("/").filter((s) => s.length > 0);
|
|
5253
|
+
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
5254
|
+
return "/" + normalised.join("/");
|
|
5255
|
+
}
|
|
5256
|
+
function walk(node, visit) {
|
|
5257
|
+
visit(node);
|
|
5258
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5259
|
+
const child = node.namedChild(i);
|
|
5260
|
+
if (child) walk(child, visit);
|
|
5261
|
+
}
|
|
5262
|
+
}
|
|
5263
|
+
function staticStringText(node) {
|
|
5264
|
+
if (node.type === "string") {
|
|
5265
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5266
|
+
const child = node.namedChild(i);
|
|
5267
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5268
|
+
}
|
|
5269
|
+
return "";
|
|
5270
|
+
}
|
|
5271
|
+
if (node.type === "template_string") {
|
|
5272
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5273
|
+
if (node.namedChild(i)?.type === "template_substitution") return null;
|
|
5274
|
+
}
|
|
5275
|
+
const raw = node.text;
|
|
5276
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
5277
|
+
}
|
|
5278
|
+
return null;
|
|
5279
|
+
}
|
|
5280
|
+
function objectStringProp(objNode, key) {
|
|
5281
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5282
|
+
const pair = objNode.namedChild(i);
|
|
5283
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5284
|
+
const k = pair.childForFieldName("key");
|
|
5285
|
+
if (!k) continue;
|
|
5286
|
+
const kText = k.type === "string" ? staticStringText(k) : k.text;
|
|
5287
|
+
if (kText !== key) continue;
|
|
5288
|
+
const v = pair.childForFieldName("value");
|
|
5289
|
+
if (v) return staticStringText(v);
|
|
5290
|
+
}
|
|
5291
|
+
return null;
|
|
5292
|
+
}
|
|
5293
|
+
function fastifyRouteMethods(objNode) {
|
|
5294
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5295
|
+
const pair = objNode.namedChild(i);
|
|
5296
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5297
|
+
const k = pair.childForFieldName("key");
|
|
5298
|
+
const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
|
|
5299
|
+
if (kText !== "method") continue;
|
|
5300
|
+
const v = pair.childForFieldName("value");
|
|
5301
|
+
if (!v) return [];
|
|
5302
|
+
if (v.type === "string" || v.type === "template_string") {
|
|
5303
|
+
const s = staticStringText(v);
|
|
5304
|
+
return s ? [s.toUpperCase()] : [];
|
|
5305
|
+
}
|
|
5306
|
+
if (v.type === "array") {
|
|
5307
|
+
const out = [];
|
|
5308
|
+
for (let j = 0; j < v.namedChildCount; j++) {
|
|
5309
|
+
const el = v.namedChild(j);
|
|
5310
|
+
if (el && (el.type === "string" || el.type === "template_string")) {
|
|
5311
|
+
const s = staticStringText(el);
|
|
5312
|
+
if (s) out.push(s.toUpperCase());
|
|
5313
|
+
}
|
|
5314
|
+
}
|
|
5315
|
+
return out;
|
|
5316
|
+
}
|
|
5317
|
+
}
|
|
5318
|
+
return [];
|
|
5319
|
+
}
|
|
5320
|
+
function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
|
|
5321
|
+
const tree = parseSource2(parser, source);
|
|
5322
|
+
const out = [];
|
|
5323
|
+
const framework = hasExpress ? "express" : "fastify";
|
|
5324
|
+
walk(tree.rootNode, (node) => {
|
|
5325
|
+
if (node.type !== "call_expression") return;
|
|
5326
|
+
const fn = node.childForFieldName("function");
|
|
5327
|
+
if (!fn || fn.type !== "member_expression") return;
|
|
5328
|
+
const prop = fn.childForFieldName("property");
|
|
5329
|
+
if (!prop) return;
|
|
5330
|
+
const method = prop.text.toLowerCase();
|
|
5331
|
+
const args = node.childForFieldName("arguments");
|
|
5332
|
+
const first = args?.namedChild(0);
|
|
5333
|
+
if (!first) return;
|
|
5334
|
+
const line = node.startPosition.row + 1;
|
|
5335
|
+
if (ROUTER_METHODS.has(method)) {
|
|
5336
|
+
const p = staticStringText(first);
|
|
5337
|
+
if (p && p.startsWith("/")) {
|
|
5338
|
+
out.push({
|
|
5339
|
+
method: method === "all" ? "ALL" : method.toUpperCase(),
|
|
5340
|
+
pathTemplate: canonicalizeTemplate(p),
|
|
5341
|
+
line,
|
|
5342
|
+
framework
|
|
5343
|
+
});
|
|
5344
|
+
}
|
|
5345
|
+
return;
|
|
5346
|
+
}
|
|
5347
|
+
if (method === "route" && hasFastify && first.type === "object") {
|
|
5348
|
+
const url = objectStringProp(first, "url");
|
|
5349
|
+
if (!url || !url.startsWith("/")) return;
|
|
5350
|
+
const methods = fastifyRouteMethods(first);
|
|
5351
|
+
const list = methods.length > 0 ? methods : ["ALL"];
|
|
5352
|
+
for (const m of list) {
|
|
5353
|
+
out.push({
|
|
5354
|
+
method: m === "ALL" ? "ALL" : m.toUpperCase(),
|
|
5355
|
+
pathTemplate: canonicalizeTemplate(url),
|
|
5356
|
+
line,
|
|
5357
|
+
framework: "fastify"
|
|
5358
|
+
});
|
|
5359
|
+
}
|
|
5360
|
+
}
|
|
5361
|
+
});
|
|
5362
|
+
return out;
|
|
5363
|
+
}
|
|
5364
|
+
function segmentsOf(relFile) {
|
|
5365
|
+
return toPosix2(relFile).split("/").filter((s) => s.length > 0);
|
|
5366
|
+
}
|
|
5367
|
+
function isNextAppRouteFile(relFile) {
|
|
5368
|
+
const segs = segmentsOf(relFile);
|
|
5369
|
+
if (!segs.includes("app")) return false;
|
|
5370
|
+
const base = segs[segs.length - 1] ?? "";
|
|
5371
|
+
return /^route\.(?:js|jsx|mjs|cjs|ts|tsx)$/.test(base);
|
|
5372
|
+
}
|
|
5373
|
+
function isNextPagesApiFile(relFile) {
|
|
5374
|
+
const segs = segmentsOf(relFile);
|
|
5375
|
+
const pagesIdx = segs.indexOf("pages");
|
|
5376
|
+
if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
|
|
5377
|
+
const base = segs[segs.length - 1] ?? "";
|
|
5378
|
+
if (/^_(app|document|middleware)\./.test(base)) return false;
|
|
5379
|
+
return JS_ROUTE_EXTENSIONS.has(import_node_path23.default.extname(base));
|
|
5380
|
+
}
|
|
5381
|
+
function nextSegment(seg) {
|
|
5382
|
+
if (seg.startsWith("(") && seg.endsWith(")")) return null;
|
|
5383
|
+
const catchAll = seg.match(/^\[\[?\.\.\.(.+?)\]?\]$/);
|
|
5384
|
+
if (catchAll) return ":" + catchAll[1];
|
|
5385
|
+
const dynamic = seg.match(/^\[(.+?)\]$/);
|
|
5386
|
+
if (dynamic) return ":" + dynamic[1];
|
|
5387
|
+
return seg;
|
|
5388
|
+
}
|
|
5389
|
+
function nextAppPathTemplate(relFile) {
|
|
5390
|
+
const segs = segmentsOf(relFile);
|
|
5391
|
+
const appIdx = segs.lastIndexOf("app");
|
|
5392
|
+
const between = segs.slice(appIdx + 1, segs.length - 1);
|
|
5393
|
+
const parts = [];
|
|
5394
|
+
for (const seg of between) {
|
|
5395
|
+
const mapped = nextSegment(seg);
|
|
5396
|
+
if (mapped !== null) parts.push(mapped);
|
|
5397
|
+
}
|
|
5398
|
+
return "/" + parts.join("/");
|
|
5399
|
+
}
|
|
5400
|
+
function nextPagesApiPathTemplate(relFile) {
|
|
5401
|
+
const segs = segmentsOf(relFile);
|
|
5402
|
+
const pagesIdx = segs.indexOf("pages");
|
|
5403
|
+
const rest = segs.slice(pagesIdx + 1);
|
|
5404
|
+
const parts = [];
|
|
5405
|
+
for (let i = 0; i < rest.length; i++) {
|
|
5406
|
+
let seg = rest[i];
|
|
5407
|
+
if (i === rest.length - 1) {
|
|
5408
|
+
seg = seg.replace(/\.(?:js|jsx|mjs|cjs|ts|tsx)$/, "");
|
|
5409
|
+
if (seg === "index") continue;
|
|
5410
|
+
}
|
|
5411
|
+
const mapped = nextSegment(seg);
|
|
5412
|
+
if (mapped !== null) parts.push(mapped);
|
|
5413
|
+
}
|
|
5414
|
+
return "/" + parts.join("/");
|
|
5415
|
+
}
|
|
5416
|
+
function nextAppMethods(root) {
|
|
5417
|
+
const out = [];
|
|
5418
|
+
walk(root, (node) => {
|
|
5419
|
+
if (node.type !== "export_statement") return;
|
|
5420
|
+
const decl = node.childForFieldName("declaration");
|
|
5421
|
+
if (!decl) return;
|
|
5422
|
+
const line = node.startPosition.row + 1;
|
|
5423
|
+
if (decl.type === "function_declaration") {
|
|
5424
|
+
const name = decl.childForFieldName("name")?.text;
|
|
5425
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
5426
|
+
return;
|
|
5427
|
+
}
|
|
5428
|
+
if (decl.type === "lexical_declaration" || decl.type === "variable_declaration") {
|
|
5429
|
+
for (let i = 0; i < decl.namedChildCount; i++) {
|
|
5430
|
+
const d = decl.namedChild(i);
|
|
5431
|
+
if (d?.type !== "variable_declarator") continue;
|
|
5432
|
+
const name = d.childForFieldName("name")?.text;
|
|
5433
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
});
|
|
5437
|
+
return out;
|
|
5438
|
+
}
|
|
5439
|
+
function nextRoutesFromFile(source, relFile, parser) {
|
|
5440
|
+
if (isNextAppRouteFile(relFile)) {
|
|
5441
|
+
const tree = parseSource2(parser, source);
|
|
5442
|
+
const template = nextAppPathTemplate(relFile);
|
|
5443
|
+
return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
|
|
5444
|
+
method,
|
|
5445
|
+
pathTemplate: canonicalizeTemplate(template),
|
|
5446
|
+
line,
|
|
5447
|
+
framework: "next"
|
|
5448
|
+
}));
|
|
5449
|
+
}
|
|
5450
|
+
if (isNextPagesApiFile(relFile)) {
|
|
5451
|
+
return [
|
|
5452
|
+
{
|
|
5453
|
+
method: "ALL",
|
|
5454
|
+
pathTemplate: canonicalizeTemplate(nextPagesApiPathTemplate(relFile)),
|
|
5455
|
+
line: 1,
|
|
5456
|
+
framework: "next"
|
|
5457
|
+
}
|
|
5458
|
+
];
|
|
5459
|
+
}
|
|
5460
|
+
return [];
|
|
5461
|
+
}
|
|
5462
|
+
async function addRoutes(graph, services) {
|
|
5463
|
+
const jsParser = makeJsParser2();
|
|
5464
|
+
let nodesAdded = 0;
|
|
5465
|
+
let edgesAdded = 0;
|
|
5466
|
+
for (const service of services) {
|
|
5467
|
+
const deps = {
|
|
5468
|
+
...service.pkg.dependencies ?? {},
|
|
5469
|
+
...service.pkg.devDependencies ?? {}
|
|
5470
|
+
};
|
|
5471
|
+
const hasExpress = deps["express"] !== void 0;
|
|
5472
|
+
const hasFastify = deps["fastify"] !== void 0;
|
|
5473
|
+
const hasNext = deps["next"] !== void 0;
|
|
5474
|
+
if (!hasExpress && !hasFastify && !hasNext) continue;
|
|
5475
|
+
const files = await loadSourceFiles(service.dir);
|
|
5476
|
+
for (const file of files) {
|
|
5477
|
+
if (isTestPath(file.path)) continue;
|
|
5478
|
+
if (!JS_ROUTE_EXTENSIONS.has(import_node_path23.default.extname(file.path))) continue;
|
|
5479
|
+
const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
|
|
5480
|
+
let routes;
|
|
5481
|
+
try {
|
|
5482
|
+
if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
5483
|
+
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
5484
|
+
} else if (hasExpress || hasFastify) {
|
|
5485
|
+
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
|
|
5486
|
+
} else {
|
|
5487
|
+
routes = [];
|
|
5488
|
+
}
|
|
5489
|
+
} catch (err) {
|
|
5490
|
+
recordExtractionError("route extraction", file.path, err);
|
|
5491
|
+
continue;
|
|
5492
|
+
}
|
|
5493
|
+
if (routes.length === 0) continue;
|
|
5494
|
+
for (const route of routes) {
|
|
5495
|
+
const rid = (0, import_types11.routeId)(service.pkg.name, route.method, route.pathTemplate);
|
|
5496
|
+
if (!graph.hasNode(rid)) {
|
|
5497
|
+
const node = {
|
|
5498
|
+
id: rid,
|
|
5499
|
+
type: import_types11.NodeType.RouteNode,
|
|
5500
|
+
name: `${route.method} ${route.pathTemplate}`,
|
|
5501
|
+
service: service.pkg.name,
|
|
5502
|
+
method: route.method,
|
|
5503
|
+
pathTemplate: route.pathTemplate,
|
|
5504
|
+
path: relFile,
|
|
5505
|
+
line: route.line,
|
|
5506
|
+
framework: route.framework,
|
|
5507
|
+
discoveredVia: "static"
|
|
5508
|
+
};
|
|
5509
|
+
graph.addNode(rid, node);
|
|
5510
|
+
nodesAdded++;
|
|
5511
|
+
}
|
|
5512
|
+
const containsId = (0, import_types11.extractedEdgeId)(service.node.id, rid, import_types11.EdgeType.CONTAINS);
|
|
5513
|
+
if (!graph.hasEdge(containsId)) {
|
|
5514
|
+
const edge = {
|
|
5515
|
+
id: containsId,
|
|
5516
|
+
source: service.node.id,
|
|
5517
|
+
target: rid,
|
|
5518
|
+
type: import_types11.EdgeType.CONTAINS,
|
|
5519
|
+
provenance: import_types11.Provenance.EXTRACTED,
|
|
5520
|
+
confidence: (0, import_types11.confidenceForExtracted)("structural"),
|
|
5521
|
+
evidence: {
|
|
5522
|
+
file: relFile,
|
|
5523
|
+
line: route.line,
|
|
5524
|
+
snippet: snippet(file.content, route.line)
|
|
5525
|
+
}
|
|
5526
|
+
};
|
|
5527
|
+
graph.addEdgeWithKey(containsId, service.node.id, rid, edge);
|
|
5528
|
+
edgesAdded++;
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
return { nodesAdded, edgesAdded };
|
|
5534
|
+
}
|
|
5535
|
+
|
|
5536
|
+
// src/extract/proto.ts
|
|
5537
|
+
init_cjs_shims();
|
|
5538
|
+
var import_node_fs16 = require("fs");
|
|
5539
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
5540
|
+
var import_types12 = require("@neat.is/types");
|
|
5541
|
+
var PROTO_EXTENSION = ".proto";
|
|
5542
|
+
function packageOf(content) {
|
|
5543
|
+
const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
|
|
5544
|
+
return m ? m[1] : null;
|
|
5545
|
+
}
|
|
5546
|
+
function lineAt(content, offset) {
|
|
5547
|
+
return content.slice(0, offset).split("\n").length;
|
|
5548
|
+
}
|
|
5549
|
+
function grpcMethodsFromProto(content, fqPackage) {
|
|
5550
|
+
const out = [];
|
|
5551
|
+
const serviceRe = /\bservice\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/g;
|
|
5552
|
+
let sm;
|
|
5553
|
+
while ((sm = serviceRe.exec(content)) !== null) {
|
|
5554
|
+
const serviceName = sm[1];
|
|
5555
|
+
const rpcService = fqPackage ? `${fqPackage}.${serviceName}` : serviceName;
|
|
5556
|
+
const bodyStart = serviceRe.lastIndex;
|
|
5557
|
+
let depth = 1;
|
|
5558
|
+
let i = bodyStart;
|
|
5559
|
+
for (; i < content.length && depth > 0; i++) {
|
|
5560
|
+
const ch = content[i];
|
|
5561
|
+
if (ch === "{") depth++;
|
|
5562
|
+
else if (ch === "}") depth--;
|
|
5563
|
+
}
|
|
5564
|
+
const body = content.slice(bodyStart, i - 1);
|
|
5565
|
+
const rpcRe = /\brpc\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
|
|
5566
|
+
let rm;
|
|
5567
|
+
while ((rm = rpcRe.exec(body)) !== null) {
|
|
5568
|
+
const rpcMethod = rm[1];
|
|
5569
|
+
const line = lineAt(content, bodyStart + rm.index);
|
|
5570
|
+
out.push({ rpcService, rpcMethod, line });
|
|
5571
|
+
}
|
|
5572
|
+
serviceRe.lastIndex = i;
|
|
5573
|
+
}
|
|
5574
|
+
return out;
|
|
5575
|
+
}
|
|
5576
|
+
async function walkProtoFiles(dir) {
|
|
5577
|
+
const out = [];
|
|
5578
|
+
async function walk3(current) {
|
|
5579
|
+
const entries = await import_node_fs16.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
5580
|
+
for (const entry2 of entries) {
|
|
5581
|
+
const full = import_node_path24.default.join(current, entry2.name);
|
|
5582
|
+
if (entry2.isDirectory()) {
|
|
5583
|
+
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
5584
|
+
if (await isPythonVenvDir(full)) continue;
|
|
5585
|
+
await walk3(full);
|
|
5586
|
+
} else if (entry2.isFile() && import_node_path24.default.extname(entry2.name) === PROTO_EXTENSION) {
|
|
5587
|
+
out.push(full);
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
await walk3(dir);
|
|
5592
|
+
return out;
|
|
5593
|
+
}
|
|
5594
|
+
async function addGrpcMethods(graph, services) {
|
|
5595
|
+
let nodesAdded = 0;
|
|
5596
|
+
let edgesAdded = 0;
|
|
5597
|
+
for (const service of services) {
|
|
5598
|
+
const protoPaths = await walkProtoFiles(service.dir);
|
|
5599
|
+
for (const protoPath of protoPaths) {
|
|
5600
|
+
if (isTestPath(protoPath)) continue;
|
|
5601
|
+
const relFile = toPosix2(import_node_path24.default.relative(service.dir, protoPath));
|
|
5602
|
+
let content;
|
|
5603
|
+
try {
|
|
5604
|
+
content = await import_node_fs16.promises.readFile(protoPath, "utf8");
|
|
5605
|
+
} catch (err) {
|
|
5606
|
+
recordExtractionError("proto extraction", protoPath, err);
|
|
5607
|
+
continue;
|
|
5608
|
+
}
|
|
5609
|
+
let methods;
|
|
5610
|
+
try {
|
|
5611
|
+
methods = grpcMethodsFromProto(content, packageOf(content));
|
|
5612
|
+
} catch (err) {
|
|
5613
|
+
recordExtractionError("proto extraction", protoPath, err);
|
|
5614
|
+
continue;
|
|
5615
|
+
}
|
|
5616
|
+
if (methods.length === 0) continue;
|
|
5617
|
+
for (const method of methods) {
|
|
5618
|
+
const mid = (0, import_types12.grpcMethodId)(method.rpcService, method.rpcMethod);
|
|
5619
|
+
if (!graph.hasNode(mid)) {
|
|
5620
|
+
const node = {
|
|
5621
|
+
id: mid,
|
|
5622
|
+
type: import_types12.NodeType.GrpcMethodNode,
|
|
5623
|
+
name: `${method.rpcService}/${method.rpcMethod}`,
|
|
5624
|
+
rpcService: method.rpcService,
|
|
5625
|
+
rpcMethod: method.rpcMethod,
|
|
5626
|
+
path: relFile,
|
|
5627
|
+
line: method.line,
|
|
5628
|
+
discoveredVia: "static"
|
|
5629
|
+
};
|
|
5630
|
+
graph.addNode(mid, node);
|
|
5631
|
+
nodesAdded++;
|
|
5632
|
+
}
|
|
5633
|
+
const containsId = (0, import_types12.extractedEdgeId)(service.node.id, mid, import_types12.EdgeType.CONTAINS);
|
|
5634
|
+
if (!graph.hasEdge(containsId)) {
|
|
5635
|
+
const edge = {
|
|
5636
|
+
id: containsId,
|
|
5637
|
+
source: service.node.id,
|
|
5638
|
+
target: mid,
|
|
5639
|
+
type: import_types12.EdgeType.CONTAINS,
|
|
5640
|
+
provenance: import_types12.Provenance.EXTRACTED,
|
|
5641
|
+
confidence: (0, import_types12.confidenceForExtracted)("structural"),
|
|
5642
|
+
evidence: {
|
|
5643
|
+
file: relFile,
|
|
5644
|
+
line: method.line,
|
|
5645
|
+
snippet: snippet(content, method.line)
|
|
5646
|
+
}
|
|
5647
|
+
};
|
|
5648
|
+
graph.addEdgeWithKey(containsId, service.node.id, mid, edge);
|
|
5649
|
+
edgesAdded++;
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5652
|
+
}
|
|
5653
|
+
}
|
|
5654
|
+
return { nodesAdded, edgesAdded };
|
|
5655
|
+
}
|
|
5656
|
+
|
|
4980
5657
|
// src/extract/calls/index.ts
|
|
4981
5658
|
init_cjs_shims();
|
|
4982
|
-
var
|
|
5659
|
+
var import_types20 = require("@neat.is/types");
|
|
4983
5660
|
|
|
4984
5661
|
// src/extract/calls/http.ts
|
|
4985
5662
|
init_cjs_shims();
|
|
4986
|
-
var
|
|
4987
|
-
var
|
|
4988
|
-
var
|
|
5663
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
5664
|
+
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
5665
|
+
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
4989
5666
|
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
4990
|
-
var
|
|
5667
|
+
var import_types13 = require("@neat.is/types");
|
|
4991
5668
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
4992
5669
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
4993
5670
|
function isInsideJsxExternalLink(node) {
|
|
@@ -5006,57 +5683,312 @@ function isInsideJsxExternalLink(node) {
|
|
|
5006
5683
|
}
|
|
5007
5684
|
cursor = cursor.parent;
|
|
5008
5685
|
}
|
|
5009
|
-
return false;
|
|
5686
|
+
return false;
|
|
5687
|
+
}
|
|
5688
|
+
function collectStringLiterals(node, out) {
|
|
5689
|
+
if (STRING_LITERAL_NODE_TYPES.has(node.type)) out.push({ text: node.text, node });
|
|
5690
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5691
|
+
const child = node.namedChild(i);
|
|
5692
|
+
if (child) collectStringLiterals(child, out);
|
|
5693
|
+
}
|
|
5694
|
+
}
|
|
5695
|
+
var PARSE_CHUNK3 = 16384;
|
|
5696
|
+
function parseSource3(parser, source) {
|
|
5697
|
+
return parser.parse(
|
|
5698
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
|
|
5699
|
+
);
|
|
5700
|
+
}
|
|
5701
|
+
function callsFromSource(source, parser, knownHosts) {
|
|
5702
|
+
const tree = parseSource3(parser, source);
|
|
5703
|
+
const literals = [];
|
|
5704
|
+
collectStringLiterals(tree.rootNode, literals);
|
|
5705
|
+
const out = [];
|
|
5706
|
+
for (const lit of literals) {
|
|
5707
|
+
if (isInsideJsxExternalLink(lit.node)) continue;
|
|
5708
|
+
for (const host of knownHosts) {
|
|
5709
|
+
if (urlMatchesHost(lit.text, host)) {
|
|
5710
|
+
out.push({ host, line: lit.node.startPosition.row + 1 });
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
}
|
|
5714
|
+
return out;
|
|
5715
|
+
}
|
|
5716
|
+
function makeJsParser3() {
|
|
5717
|
+
const p = new import_tree_sitter3.default();
|
|
5718
|
+
p.setLanguage(import_tree_sitter_javascript3.default);
|
|
5719
|
+
return p;
|
|
5720
|
+
}
|
|
5721
|
+
function makePyParser2() {
|
|
5722
|
+
const p = new import_tree_sitter3.default();
|
|
5723
|
+
p.setLanguage(import_tree_sitter_python2.default);
|
|
5724
|
+
return p;
|
|
5725
|
+
}
|
|
5726
|
+
async function addHttpCallEdges(graph, services) {
|
|
5727
|
+
const jsParser = makeJsParser3();
|
|
5728
|
+
const pyParser = makePyParser2();
|
|
5729
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
5730
|
+
let nodesAdded = 0;
|
|
5731
|
+
let edgesAdded = 0;
|
|
5732
|
+
for (const service of services) {
|
|
5733
|
+
const files = await loadSourceFiles(service.dir);
|
|
5734
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5735
|
+
for (const file of files) {
|
|
5736
|
+
if (isTestPath(file.path)) continue;
|
|
5737
|
+
const parser = import_node_path25.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
5738
|
+
let sites;
|
|
5739
|
+
try {
|
|
5740
|
+
sites = callsFromSource(file.content, parser, knownHosts);
|
|
5741
|
+
} catch (err) {
|
|
5742
|
+
recordExtractionError("http call extraction", file.path, err);
|
|
5743
|
+
continue;
|
|
5744
|
+
}
|
|
5745
|
+
if (sites.length === 0) continue;
|
|
5746
|
+
const relFile = toPosix2(import_node_path25.default.relative(service.dir, file.path));
|
|
5747
|
+
for (const site of sites) {
|
|
5748
|
+
const targetId = hostToNodeId.get(site.host);
|
|
5749
|
+
if (!targetId || targetId === service.node.id) continue;
|
|
5750
|
+
const dedupKey = `${relFile}|${targetId}`;
|
|
5751
|
+
if (seen.has(dedupKey)) continue;
|
|
5752
|
+
seen.add(dedupKey);
|
|
5753
|
+
const confidence = (0, import_types13.confidenceForExtracted)("url-literal-service-target");
|
|
5754
|
+
const ev = {
|
|
5755
|
+
file: relFile,
|
|
5756
|
+
line: site.line,
|
|
5757
|
+
snippet: snippet(file.content, site.line)
|
|
5758
|
+
};
|
|
5759
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5760
|
+
graph,
|
|
5761
|
+
service.pkg.name,
|
|
5762
|
+
service.node.id,
|
|
5763
|
+
relFile
|
|
5764
|
+
);
|
|
5765
|
+
nodesAdded += n;
|
|
5766
|
+
edgesAdded += e;
|
|
5767
|
+
if (!(0, import_types13.passesExtractedFloor)(confidence)) {
|
|
5768
|
+
noteExtractedDropped({
|
|
5769
|
+
source: fileNodeId,
|
|
5770
|
+
target: targetId,
|
|
5771
|
+
type: import_types13.EdgeType.CALLS,
|
|
5772
|
+
confidence,
|
|
5773
|
+
confidenceKind: "url-literal-service-target",
|
|
5774
|
+
evidence: ev
|
|
5775
|
+
});
|
|
5776
|
+
continue;
|
|
5777
|
+
}
|
|
5778
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types13.EdgeType.CALLS);
|
|
5779
|
+
if (!graph.hasEdge(edgeId)) {
|
|
5780
|
+
const edge = {
|
|
5781
|
+
id: edgeId,
|
|
5782
|
+
source: fileNodeId,
|
|
5783
|
+
target: targetId,
|
|
5784
|
+
type: import_types13.EdgeType.CALLS,
|
|
5785
|
+
provenance: import_types13.Provenance.EXTRACTED,
|
|
5786
|
+
confidence,
|
|
5787
|
+
evidence: ev
|
|
5788
|
+
};
|
|
5789
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, targetId, edge);
|
|
5790
|
+
edgesAdded++;
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
return { nodesAdded, edgesAdded };
|
|
5796
|
+
}
|
|
5797
|
+
|
|
5798
|
+
// src/extract/calls/route-match.ts
|
|
5799
|
+
init_cjs_shims();
|
|
5800
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
5801
|
+
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
5802
|
+
var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5803
|
+
var import_types14 = require("@neat.is/types");
|
|
5804
|
+
var PARSE_CHUNK4 = 16384;
|
|
5805
|
+
function parseSource4(parser, source) {
|
|
5806
|
+
return parser.parse(
|
|
5807
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK4)
|
|
5808
|
+
);
|
|
5809
|
+
}
|
|
5810
|
+
function makeJsParser4() {
|
|
5811
|
+
const p = new import_tree_sitter4.default();
|
|
5812
|
+
p.setLanguage(import_tree_sitter_javascript4.default);
|
|
5813
|
+
return p;
|
|
5814
|
+
}
|
|
5815
|
+
var JS_CLIENT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5816
|
+
var AXIOS_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "head", "options", "request"]);
|
|
5817
|
+
function walk2(node, visit) {
|
|
5818
|
+
visit(node);
|
|
5819
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5820
|
+
const child = node.namedChild(i);
|
|
5821
|
+
if (child) walk2(child, visit);
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5824
|
+
function reconstructUrl(node) {
|
|
5825
|
+
if (node.type === "string") {
|
|
5826
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5827
|
+
const child = node.namedChild(i);
|
|
5828
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5829
|
+
}
|
|
5830
|
+
return "";
|
|
5831
|
+
}
|
|
5832
|
+
if (node.type === "template_string") {
|
|
5833
|
+
let out = "";
|
|
5834
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5835
|
+
const child = node.namedChild(i);
|
|
5836
|
+
if (!child) continue;
|
|
5837
|
+
if (child.type === "string_fragment") out += child.text;
|
|
5838
|
+
else if (child.type === "template_substitution") out += ":param";
|
|
5839
|
+
}
|
|
5840
|
+
if (out.length === 0) {
|
|
5841
|
+
const raw = node.text;
|
|
5842
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
5843
|
+
}
|
|
5844
|
+
return out;
|
|
5845
|
+
}
|
|
5846
|
+
return null;
|
|
5847
|
+
}
|
|
5848
|
+
function methodFromOptions(objNode) {
|
|
5849
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5850
|
+
const pair = objNode.namedChild(i);
|
|
5851
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5852
|
+
const k = pair.childForFieldName("key");
|
|
5853
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
5854
|
+
if (kText !== "method") continue;
|
|
5855
|
+
const v = pair.childForFieldName("value");
|
|
5856
|
+
if (v && (v.type === "string" || v.type === "template_string")) {
|
|
5857
|
+
const s = stringText(v);
|
|
5858
|
+
return s ? s.toUpperCase() : void 0;
|
|
5859
|
+
}
|
|
5860
|
+
}
|
|
5861
|
+
return void 0;
|
|
5862
|
+
}
|
|
5863
|
+
function stringText(node) {
|
|
5864
|
+
if (node.type === "string") {
|
|
5865
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5866
|
+
const child = node.namedChild(i);
|
|
5867
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5868
|
+
}
|
|
5869
|
+
return "";
|
|
5870
|
+
}
|
|
5871
|
+
return null;
|
|
5872
|
+
}
|
|
5873
|
+
function urlNodeFromConfig(objNode) {
|
|
5874
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5875
|
+
const pair = objNode.namedChild(i);
|
|
5876
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5877
|
+
const k = pair.childForFieldName("key");
|
|
5878
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
5879
|
+
if (kText === "url") return pair.childForFieldName("value");
|
|
5880
|
+
}
|
|
5881
|
+
return null;
|
|
5010
5882
|
}
|
|
5011
|
-
function
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
const
|
|
5015
|
-
|
|
5883
|
+
function pathOf(urlStr) {
|
|
5884
|
+
try {
|
|
5885
|
+
const candidate = urlStr.startsWith("//") ? `http:${urlStr}` : urlStr;
|
|
5886
|
+
const parsed = new URL(candidate);
|
|
5887
|
+
return parsed.pathname || "/";
|
|
5888
|
+
} catch {
|
|
5889
|
+
return null;
|
|
5016
5890
|
}
|
|
5017
5891
|
}
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5892
|
+
function matchHost(urlStr, knownHosts) {
|
|
5893
|
+
for (const host of knownHosts) {
|
|
5894
|
+
if (urlMatchesHost(urlStr, host)) return host;
|
|
5895
|
+
}
|
|
5896
|
+
return null;
|
|
5023
5897
|
}
|
|
5024
|
-
function
|
|
5025
|
-
const tree =
|
|
5026
|
-
const literals = [];
|
|
5027
|
-
collectStringLiterals(tree.rootNode, literals);
|
|
5898
|
+
function clientCallSitesFromSource(source, parser, knownHosts) {
|
|
5899
|
+
const tree = parseSource4(parser, source);
|
|
5028
5900
|
const out = [];
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5901
|
+
const push = (urlNode, method, callNode) => {
|
|
5902
|
+
const urlStr = reconstructUrl(urlNode);
|
|
5903
|
+
if (!urlStr) return;
|
|
5904
|
+
const host = matchHost(urlStr, knownHosts);
|
|
5905
|
+
if (!host) return;
|
|
5906
|
+
const p = pathOf(urlStr);
|
|
5907
|
+
if (p === null) return;
|
|
5908
|
+
const line = callNode.startPosition.row + 1;
|
|
5909
|
+
out.push({
|
|
5910
|
+
host,
|
|
5911
|
+
method,
|
|
5912
|
+
pathTemplate: p,
|
|
5913
|
+
line,
|
|
5914
|
+
snippet: snippet(source, line)
|
|
5915
|
+
});
|
|
5916
|
+
};
|
|
5917
|
+
walk2(tree.rootNode, (node) => {
|
|
5918
|
+
if (node.type !== "call_expression") return;
|
|
5919
|
+
const fn = node.childForFieldName("function");
|
|
5920
|
+
if (!fn) return;
|
|
5921
|
+
const args = node.childForFieldName("arguments");
|
|
5922
|
+
const first = args?.namedChild(0);
|
|
5923
|
+
if (!first) return;
|
|
5924
|
+
const fnName = fn.type === "identifier" ? fn.text : fn.type === "member_expression" ? fn.childForFieldName("property")?.text ?? "" : "";
|
|
5925
|
+
if (fn.type === "identifier" && fnName === "fetch") {
|
|
5926
|
+
const opts = args?.namedChild(1);
|
|
5927
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
5928
|
+
push(first, method, node);
|
|
5929
|
+
return;
|
|
5930
|
+
}
|
|
5931
|
+
if (fn.type === "identifier" && fnName === "axios") {
|
|
5932
|
+
if (first.type === "object") {
|
|
5933
|
+
const urlNode = urlNodeFromConfig(first);
|
|
5934
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
5935
|
+
} else {
|
|
5936
|
+
const opts = args?.namedChild(1);
|
|
5937
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
5938
|
+
push(first, method, node);
|
|
5034
5939
|
}
|
|
5940
|
+
return;
|
|
5035
5941
|
}
|
|
5036
|
-
|
|
5942
|
+
if (fn.type === "member_expression") {
|
|
5943
|
+
const obj = fn.childForFieldName("object");
|
|
5944
|
+
const objName = obj?.text ?? "";
|
|
5945
|
+
if (objName === "axios" && AXIOS_METHODS.has(fnName)) {
|
|
5946
|
+
if (fnName === "request" && first.type === "object") {
|
|
5947
|
+
const urlNode = urlNodeFromConfig(first);
|
|
5948
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
5949
|
+
} else {
|
|
5950
|
+
push(first, fnName.toUpperCase(), node);
|
|
5951
|
+
}
|
|
5952
|
+
return;
|
|
5953
|
+
}
|
|
5954
|
+
if ((objName === "http" || objName === "https") && (fnName === "request" || fnName === "get")) {
|
|
5955
|
+
const opts = args?.namedChild(1);
|
|
5956
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? (fnName === "get" ? "GET" : "GET") : "GET";
|
|
5957
|
+
push(first, method, node);
|
|
5958
|
+
return;
|
|
5959
|
+
}
|
|
5960
|
+
}
|
|
5961
|
+
});
|
|
5037
5962
|
return out;
|
|
5038
5963
|
}
|
|
5039
|
-
function
|
|
5040
|
-
const
|
|
5041
|
-
|
|
5042
|
-
|
|
5964
|
+
function buildRouteIndex(graph) {
|
|
5965
|
+
const index = /* @__PURE__ */ new Map();
|
|
5966
|
+
graph.forEachNode((_id, attrs) => {
|
|
5967
|
+
const node = attrs;
|
|
5968
|
+
if (node.type !== import_types14.NodeType.RouteNode) return;
|
|
5969
|
+
const route = attrs;
|
|
5970
|
+
const owner = (0, import_types14.serviceId)(route.service);
|
|
5971
|
+
const entry2 = {
|
|
5972
|
+
method: route.method.toUpperCase(),
|
|
5973
|
+
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
5974
|
+
routeNodeId: route.id
|
|
5975
|
+
};
|
|
5976
|
+
const list = index.get(owner);
|
|
5977
|
+
if (list) list.push(entry2);
|
|
5978
|
+
else index.set(owner, [entry2]);
|
|
5979
|
+
});
|
|
5980
|
+
return index;
|
|
5043
5981
|
}
|
|
5044
|
-
function
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5982
|
+
function findRoute(entries, method, normalizedPath) {
|
|
5983
|
+
return entries.find(
|
|
5984
|
+
(e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || method === void 0 || e.method === method)
|
|
5985
|
+
);
|
|
5048
5986
|
}
|
|
5049
|
-
async function
|
|
5050
|
-
const jsParser =
|
|
5051
|
-
const
|
|
5052
|
-
const
|
|
5053
|
-
|
|
5054
|
-
for (const service of services) {
|
|
5055
|
-
knownHosts.add(import_node_path23.default.basename(service.dir));
|
|
5056
|
-
knownHosts.add(service.pkg.name);
|
|
5057
|
-
hostToNodeId.set(import_node_path23.default.basename(service.dir), service.node.id);
|
|
5058
|
-
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
5059
|
-
}
|
|
5987
|
+
async function addRouteCallEdges(graph, services) {
|
|
5988
|
+
const jsParser = makeJsParser4();
|
|
5989
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
5990
|
+
const routeIndex = buildRouteIndex(graph);
|
|
5991
|
+
if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
5060
5992
|
let nodesAdded = 0;
|
|
5061
5993
|
let edgesAdded = 0;
|
|
5062
5994
|
for (const service of services) {
|
|
@@ -5064,28 +5996,27 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5064
5996
|
const seen = /* @__PURE__ */ new Set();
|
|
5065
5997
|
for (const file of files) {
|
|
5066
5998
|
if (isTestPath(file.path)) continue;
|
|
5067
|
-
|
|
5999
|
+
if (!JS_CLIENT_EXTENSIONS.has(import_node_path26.default.extname(file.path))) continue;
|
|
5068
6000
|
let sites;
|
|
5069
6001
|
try {
|
|
5070
|
-
sites =
|
|
6002
|
+
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
5071
6003
|
} catch (err) {
|
|
5072
|
-
recordExtractionError("
|
|
6004
|
+
recordExtractionError("route-match call extraction", file.path, err);
|
|
5073
6005
|
continue;
|
|
5074
6006
|
}
|
|
5075
6007
|
if (sites.length === 0) continue;
|
|
5076
|
-
const relFile = toPosix2(
|
|
6008
|
+
const relFile = toPosix2(import_node_path26.default.relative(service.dir, file.path));
|
|
5077
6009
|
for (const site of sites) {
|
|
5078
|
-
const
|
|
5079
|
-
if (!
|
|
5080
|
-
const
|
|
6010
|
+
const serverServiceId = hostToNodeId.get(site.host);
|
|
6011
|
+
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
6012
|
+
const entries = routeIndex.get(serverServiceId);
|
|
6013
|
+
if (!entries) continue;
|
|
6014
|
+
const normalizedPath = normalizePathTemplate(site.pathTemplate);
|
|
6015
|
+
const match = findRoute(entries, site.method, normalizedPath);
|
|
6016
|
+
if (!match) continue;
|
|
6017
|
+
const dedupKey = `${relFile}|${match.routeNodeId}`;
|
|
5081
6018
|
if (seen.has(dedupKey)) continue;
|
|
5082
6019
|
seen.add(dedupKey);
|
|
5083
|
-
const confidence = (0, import_types11.confidenceForExtracted)("url-literal-service-target");
|
|
5084
|
-
const ev = {
|
|
5085
|
-
file: relFile,
|
|
5086
|
-
line: site.line,
|
|
5087
|
-
snippet: snippet(file.content, site.line)
|
|
5088
|
-
};
|
|
5089
6020
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5090
6021
|
graph,
|
|
5091
6022
|
service.pkg.name,
|
|
@@ -5094,29 +6025,37 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5094
6025
|
);
|
|
5095
6026
|
nodesAdded += n;
|
|
5096
6027
|
edgesAdded += e;
|
|
5097
|
-
|
|
6028
|
+
const confidence = (0, import_types14.confidenceForExtracted)("verified-call-site");
|
|
6029
|
+
const ev = {
|
|
6030
|
+
file: relFile,
|
|
6031
|
+
line: site.line,
|
|
6032
|
+
snippet: site.snippet,
|
|
6033
|
+
method: site.method ?? match.method,
|
|
6034
|
+
pathTemplate: site.pathTemplate
|
|
6035
|
+
};
|
|
6036
|
+
if (!(0, import_types14.passesExtractedFloor)(confidence)) {
|
|
5098
6037
|
noteExtractedDropped({
|
|
5099
6038
|
source: fileNodeId,
|
|
5100
|
-
target:
|
|
5101
|
-
type:
|
|
6039
|
+
target: match.routeNodeId,
|
|
6040
|
+
type: import_types14.EdgeType.CALLS,
|
|
5102
6041
|
confidence,
|
|
5103
|
-
confidenceKind: "
|
|
6042
|
+
confidenceKind: "verified-call-site",
|
|
5104
6043
|
evidence: ev
|
|
5105
6044
|
});
|
|
5106
6045
|
continue;
|
|
5107
6046
|
}
|
|
5108
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId,
|
|
6047
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types14.EdgeType.CALLS);
|
|
5109
6048
|
if (!graph.hasEdge(edgeId)) {
|
|
5110
6049
|
const edge = {
|
|
5111
6050
|
id: edgeId,
|
|
5112
6051
|
source: fileNodeId,
|
|
5113
|
-
target:
|
|
5114
|
-
type:
|
|
5115
|
-
provenance:
|
|
6052
|
+
target: match.routeNodeId,
|
|
6053
|
+
type: import_types14.EdgeType.CALLS,
|
|
6054
|
+
provenance: import_types14.Provenance.EXTRACTED,
|
|
5116
6055
|
confidence,
|
|
5117
6056
|
evidence: ev
|
|
5118
6057
|
};
|
|
5119
|
-
graph.addEdgeWithKey(edgeId, fileNodeId,
|
|
6058
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, match.routeNodeId, edge);
|
|
5120
6059
|
edgesAdded++;
|
|
5121
6060
|
}
|
|
5122
6061
|
}
|
|
@@ -5127,8 +6066,8 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5127
6066
|
|
|
5128
6067
|
// src/extract/calls/kafka.ts
|
|
5129
6068
|
init_cjs_shims();
|
|
5130
|
-
var
|
|
5131
|
-
var
|
|
6069
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
6070
|
+
var import_types15 = require("@neat.is/types");
|
|
5132
6071
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5133
6072
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
5134
6073
|
function findAll(re, text) {
|
|
@@ -5149,7 +6088,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5149
6088
|
seen.add(key);
|
|
5150
6089
|
const line = lineOf(file.content, topic);
|
|
5151
6090
|
out.push({
|
|
5152
|
-
infraId: (0,
|
|
6091
|
+
infraId: (0, import_types15.infraId)("kafka-topic", topic),
|
|
5153
6092
|
name: topic,
|
|
5154
6093
|
kind: "kafka-topic",
|
|
5155
6094
|
edgeType,
|
|
@@ -5158,7 +6097,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5158
6097
|
// tier (ADR-066).
|
|
5159
6098
|
confidenceKind: "verified-call-site",
|
|
5160
6099
|
evidence: {
|
|
5161
|
-
file:
|
|
6100
|
+
file: import_node_path27.default.relative(serviceDir, file.path),
|
|
5162
6101
|
line,
|
|
5163
6102
|
snippet: snippet(file.content, line)
|
|
5164
6103
|
}
|
|
@@ -5171,8 +6110,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5171
6110
|
|
|
5172
6111
|
// src/extract/calls/redis.ts
|
|
5173
6112
|
init_cjs_shims();
|
|
5174
|
-
var
|
|
5175
|
-
var
|
|
6113
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
6114
|
+
var import_types16 = require("@neat.is/types");
|
|
5176
6115
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
5177
6116
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
5178
6117
|
const out = [];
|
|
@@ -5185,7 +6124,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5185
6124
|
seen.add(host);
|
|
5186
6125
|
const line = lineOf(file.content, host);
|
|
5187
6126
|
out.push({
|
|
5188
|
-
infraId: (0,
|
|
6127
|
+
infraId: (0, import_types16.infraId)("redis", host),
|
|
5189
6128
|
name: host,
|
|
5190
6129
|
kind: "redis",
|
|
5191
6130
|
edgeType: "CALLS",
|
|
@@ -5194,7 +6133,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5194
6133
|
// support tier (ADR-066).
|
|
5195
6134
|
confidenceKind: "url-with-structural-support",
|
|
5196
6135
|
evidence: {
|
|
5197
|
-
file:
|
|
6136
|
+
file: import_node_path28.default.relative(serviceDir, file.path),
|
|
5198
6137
|
line,
|
|
5199
6138
|
snippet: snippet(file.content, line)
|
|
5200
6139
|
}
|
|
@@ -5205,8 +6144,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5205
6144
|
|
|
5206
6145
|
// src/extract/calls/aws.ts
|
|
5207
6146
|
init_cjs_shims();
|
|
5208
|
-
var
|
|
5209
|
-
var
|
|
6147
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
6148
|
+
var import_types17 = require("@neat.is/types");
|
|
5210
6149
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5211
6150
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5212
6151
|
function hasMarker(text, markers) {
|
|
@@ -5230,7 +6169,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5230
6169
|
seen.add(key);
|
|
5231
6170
|
const line = lineOf(file.content, name);
|
|
5232
6171
|
out.push({
|
|
5233
|
-
infraId: (0,
|
|
6172
|
+
infraId: (0, import_types17.infraId)(kind, name),
|
|
5234
6173
|
name,
|
|
5235
6174
|
kind,
|
|
5236
6175
|
edgeType: "CALLS",
|
|
@@ -5239,7 +6178,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5239
6178
|
// (ADR-066).
|
|
5240
6179
|
confidenceKind: "verified-call-site",
|
|
5241
6180
|
evidence: {
|
|
5242
|
-
file:
|
|
6181
|
+
file: import_node_path29.default.relative(serviceDir, file.path),
|
|
5243
6182
|
line,
|
|
5244
6183
|
snippet: snippet(file.content, line)
|
|
5245
6184
|
}
|
|
@@ -5264,8 +6203,8 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5264
6203
|
|
|
5265
6204
|
// src/extract/calls/grpc.ts
|
|
5266
6205
|
init_cjs_shims();
|
|
5267
|
-
var
|
|
5268
|
-
var
|
|
6206
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
6207
|
+
var import_types18 = require("@neat.is/types");
|
|
5269
6208
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
5270
6209
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
5271
6210
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -5314,7 +6253,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5314
6253
|
const { kind } = classified;
|
|
5315
6254
|
const line = lineOf(file.content, m[0]);
|
|
5316
6255
|
out.push({
|
|
5317
|
-
infraId: (0,
|
|
6256
|
+
infraId: (0, import_types18.infraId)(kind, name),
|
|
5318
6257
|
name,
|
|
5319
6258
|
kind,
|
|
5320
6259
|
edgeType: "CALLS",
|
|
@@ -5323,7 +6262,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5323
6262
|
// tier (ADR-066).
|
|
5324
6263
|
confidenceKind: "verified-call-site",
|
|
5325
6264
|
evidence: {
|
|
5326
|
-
file:
|
|
6265
|
+
file: import_node_path30.default.relative(serviceDir, file.path),
|
|
5327
6266
|
line,
|
|
5328
6267
|
snippet: snippet(file.content, line)
|
|
5329
6268
|
}
|
|
@@ -5334,8 +6273,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5334
6273
|
|
|
5335
6274
|
// src/extract/calls/supabase.ts
|
|
5336
6275
|
init_cjs_shims();
|
|
5337
|
-
var
|
|
5338
|
-
var
|
|
6276
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
6277
|
+
var import_types19 = require("@neat.is/types");
|
|
5339
6278
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
5340
6279
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
5341
6280
|
var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
|
|
@@ -5373,7 +6312,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5373
6312
|
seen.add(name);
|
|
5374
6313
|
const line = lineOf(file.content, m[0]);
|
|
5375
6314
|
out.push({
|
|
5376
|
-
infraId: (0,
|
|
6315
|
+
infraId: (0, import_types19.infraId)("supabase", name),
|
|
5377
6316
|
name,
|
|
5378
6317
|
kind: "supabase",
|
|
5379
6318
|
edgeType: "CALLS",
|
|
@@ -5383,7 +6322,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5383
6322
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
5384
6323
|
confidenceKind: "verified-call-site",
|
|
5385
6324
|
evidence: {
|
|
5386
|
-
file:
|
|
6325
|
+
file: import_node_path31.default.relative(serviceDir, file.path),
|
|
5387
6326
|
line,
|
|
5388
6327
|
snippet: snippet(file.content, line)
|
|
5389
6328
|
}
|
|
@@ -5396,11 +6335,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5396
6335
|
function edgeTypeFromEndpoint(ep) {
|
|
5397
6336
|
switch (ep.edgeType) {
|
|
5398
6337
|
case "PUBLISHES_TO":
|
|
5399
|
-
return
|
|
6338
|
+
return import_types20.EdgeType.PUBLISHES_TO;
|
|
5400
6339
|
case "CONSUMES_FROM":
|
|
5401
|
-
return
|
|
6340
|
+
return import_types20.EdgeType.CONSUMES_FROM;
|
|
5402
6341
|
default:
|
|
5403
|
-
return
|
|
6342
|
+
return import_types20.EdgeType.CALLS;
|
|
5404
6343
|
}
|
|
5405
6344
|
}
|
|
5406
6345
|
function isAwsKind(kind) {
|
|
@@ -5428,7 +6367,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5428
6367
|
if (!graph.hasNode(ep.infraId)) {
|
|
5429
6368
|
const node = {
|
|
5430
6369
|
id: ep.infraId,
|
|
5431
|
-
type:
|
|
6370
|
+
type: import_types20.NodeType.InfraNode,
|
|
5432
6371
|
name: ep.name,
|
|
5433
6372
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
5434
6373
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -5440,7 +6379,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5440
6379
|
nodesAdded++;
|
|
5441
6380
|
}
|
|
5442
6381
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
5443
|
-
const confidence = (0,
|
|
6382
|
+
const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
|
|
5444
6383
|
const relFile = toPosix2(ep.evidence.file);
|
|
5445
6384
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5446
6385
|
graph,
|
|
@@ -5450,7 +6389,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5450
6389
|
);
|
|
5451
6390
|
nodesAdded += n;
|
|
5452
6391
|
edgesAdded += e;
|
|
5453
|
-
if (!(0,
|
|
6392
|
+
if (!(0, import_types20.passesExtractedFloor)(confidence)) {
|
|
5454
6393
|
noteExtractedDropped({
|
|
5455
6394
|
source: fileNodeId,
|
|
5456
6395
|
target: ep.infraId,
|
|
@@ -5470,7 +6409,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5470
6409
|
source: fileNodeId,
|
|
5471
6410
|
target: ep.infraId,
|
|
5472
6411
|
type: edgeType,
|
|
5473
|
-
provenance:
|
|
6412
|
+
provenance: import_types20.Provenance.EXTRACTED,
|
|
5474
6413
|
confidence,
|
|
5475
6414
|
evidence: ep.evidence
|
|
5476
6415
|
};
|
|
@@ -5484,9 +6423,10 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5484
6423
|
async function addCallEdges(graph, services) {
|
|
5485
6424
|
const http2 = await addHttpCallEdges(graph, services);
|
|
5486
6425
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
6426
|
+
const routes = await addRouteCallEdges(graph, services);
|
|
5487
6427
|
return {
|
|
5488
|
-
nodesAdded: http2.nodesAdded + ext.nodesAdded,
|
|
5489
|
-
edgesAdded: http2.edgesAdded + ext.edgesAdded
|
|
6428
|
+
nodesAdded: http2.nodesAdded + ext.nodesAdded + routes.nodesAdded,
|
|
6429
|
+
edgesAdded: http2.edgesAdded + ext.edgesAdded + routes.edgesAdded
|
|
5490
6430
|
};
|
|
5491
6431
|
}
|
|
5492
6432
|
|
|
@@ -5495,16 +6435,16 @@ init_cjs_shims();
|
|
|
5495
6435
|
|
|
5496
6436
|
// src/extract/infra/docker-compose.ts
|
|
5497
6437
|
init_cjs_shims();
|
|
5498
|
-
var
|
|
5499
|
-
var
|
|
6438
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
6439
|
+
var import_types22 = require("@neat.is/types");
|
|
5500
6440
|
|
|
5501
6441
|
// src/extract/infra/shared.ts
|
|
5502
6442
|
init_cjs_shims();
|
|
5503
|
-
var
|
|
6443
|
+
var import_types21 = require("@neat.is/types");
|
|
5504
6444
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
5505
6445
|
return {
|
|
5506
|
-
id: (0,
|
|
5507
|
-
type:
|
|
6446
|
+
id: (0, import_types21.infraId)(kind, name),
|
|
6447
|
+
type: import_types21.NodeType.InfraNode,
|
|
5508
6448
|
name,
|
|
5509
6449
|
provider,
|
|
5510
6450
|
kind,
|
|
@@ -5533,7 +6473,7 @@ function dependsOnList(value) {
|
|
|
5533
6473
|
}
|
|
5534
6474
|
function serviceNameToServiceNode(name, services) {
|
|
5535
6475
|
for (const s of services) {
|
|
5536
|
-
if (s.node.name === name ||
|
|
6476
|
+
if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
|
|
5537
6477
|
}
|
|
5538
6478
|
return null;
|
|
5539
6479
|
}
|
|
@@ -5542,7 +6482,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5542
6482
|
let edgesAdded = 0;
|
|
5543
6483
|
let composePath = null;
|
|
5544
6484
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
5545
|
-
const abs =
|
|
6485
|
+
const abs = import_node_path32.default.join(scanPath, name);
|
|
5546
6486
|
if (await exists(abs)) {
|
|
5547
6487
|
composePath = abs;
|
|
5548
6488
|
break;
|
|
@@ -5555,13 +6495,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5555
6495
|
} catch (err) {
|
|
5556
6496
|
recordExtractionError(
|
|
5557
6497
|
"infra docker-compose",
|
|
5558
|
-
|
|
6498
|
+
import_node_path32.default.relative(scanPath, composePath),
|
|
5559
6499
|
err
|
|
5560
6500
|
);
|
|
5561
6501
|
return { nodesAdded, edgesAdded };
|
|
5562
6502
|
}
|
|
5563
6503
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
5564
|
-
const evidenceFile =
|
|
6504
|
+
const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
|
|
5565
6505
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
5566
6506
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
5567
6507
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -5583,15 +6523,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5583
6523
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
5584
6524
|
const targetId = composeNameToNodeId.get(dep);
|
|
5585
6525
|
if (!targetId) continue;
|
|
5586
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
6526
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
|
|
5587
6527
|
if (graph.hasEdge(edgeId)) continue;
|
|
5588
6528
|
const edge = {
|
|
5589
6529
|
id: edgeId,
|
|
5590
6530
|
source: sourceId,
|
|
5591
6531
|
target: targetId,
|
|
5592
|
-
type:
|
|
5593
|
-
provenance:
|
|
5594
|
-
confidence: (0,
|
|
6532
|
+
type: import_types22.EdgeType.DEPENDS_ON,
|
|
6533
|
+
provenance: import_types22.Provenance.EXTRACTED,
|
|
6534
|
+
confidence: (0, import_types22.confidenceForExtracted)("structural"),
|
|
5595
6535
|
evidence: { file: evidenceFile }
|
|
5596
6536
|
};
|
|
5597
6537
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -5603,9 +6543,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5603
6543
|
|
|
5604
6544
|
// src/extract/infra/dockerfile.ts
|
|
5605
6545
|
init_cjs_shims();
|
|
5606
|
-
var
|
|
5607
|
-
var
|
|
5608
|
-
var
|
|
6546
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
6547
|
+
var import_node_fs17 = require("fs");
|
|
6548
|
+
var import_types23 = require("@neat.is/types");
|
|
5609
6549
|
function readDockerfile(content) {
|
|
5610
6550
|
let image = null;
|
|
5611
6551
|
const ports = [];
|
|
@@ -5634,15 +6574,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5634
6574
|
let nodesAdded = 0;
|
|
5635
6575
|
let edgesAdded = 0;
|
|
5636
6576
|
for (const service of services) {
|
|
5637
|
-
const dockerfilePath =
|
|
6577
|
+
const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
|
|
5638
6578
|
if (!await exists(dockerfilePath)) continue;
|
|
5639
6579
|
let content;
|
|
5640
6580
|
try {
|
|
5641
|
-
content = await
|
|
6581
|
+
content = await import_node_fs17.promises.readFile(dockerfilePath, "utf8");
|
|
5642
6582
|
} catch (err) {
|
|
5643
6583
|
recordExtractionError(
|
|
5644
6584
|
"infra dockerfile",
|
|
5645
|
-
|
|
6585
|
+
import_node_path33.default.relative(scanPath, dockerfilePath),
|
|
5646
6586
|
err
|
|
5647
6587
|
);
|
|
5648
6588
|
continue;
|
|
@@ -5654,8 +6594,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5654
6594
|
graph.addNode(node.id, node);
|
|
5655
6595
|
nodesAdded++;
|
|
5656
6596
|
}
|
|
5657
|
-
const relDockerfile = toPosix2(
|
|
5658
|
-
const evidenceFile = toPosix2(
|
|
6597
|
+
const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
|
|
6598
|
+
const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
|
|
5659
6599
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
5660
6600
|
graph,
|
|
5661
6601
|
service.pkg.name,
|
|
@@ -5664,15 +6604,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5664
6604
|
);
|
|
5665
6605
|
nodesAdded += fn;
|
|
5666
6606
|
edgesAdded += fe;
|
|
5667
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id,
|
|
6607
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
|
|
5668
6608
|
if (!graph.hasEdge(edgeId)) {
|
|
5669
6609
|
const edge = {
|
|
5670
6610
|
id: edgeId,
|
|
5671
6611
|
source: fileNodeId,
|
|
5672
6612
|
target: node.id,
|
|
5673
|
-
type:
|
|
5674
|
-
provenance:
|
|
5675
|
-
confidence: (0,
|
|
6613
|
+
type: import_types23.EdgeType.RUNS_ON,
|
|
6614
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
6615
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
5676
6616
|
evidence: {
|
|
5677
6617
|
file: evidenceFile,
|
|
5678
6618
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -5687,15 +6627,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5687
6627
|
graph.addNode(portNode.id, portNode);
|
|
5688
6628
|
nodesAdded++;
|
|
5689
6629
|
}
|
|
5690
|
-
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id,
|
|
6630
|
+
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
|
|
5691
6631
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
5692
6632
|
const portEdge = {
|
|
5693
6633
|
id: portEdgeId,
|
|
5694
6634
|
source: fileNodeId,
|
|
5695
6635
|
target: portNode.id,
|
|
5696
|
-
type:
|
|
5697
|
-
provenance:
|
|
5698
|
-
confidence: (0,
|
|
6636
|
+
type: import_types23.EdgeType.CONNECTS_TO,
|
|
6637
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
6638
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
5699
6639
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
5700
6640
|
};
|
|
5701
6641
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -5707,23 +6647,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5707
6647
|
|
|
5708
6648
|
// src/extract/infra/terraform.ts
|
|
5709
6649
|
init_cjs_shims();
|
|
5710
|
-
var
|
|
5711
|
-
var
|
|
5712
|
-
var
|
|
6650
|
+
var import_node_fs18 = require("fs");
|
|
6651
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
6652
|
+
var import_types24 = require("@neat.is/types");
|
|
5713
6653
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
5714
6654
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
5715
6655
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
5716
6656
|
if (depth > max) return [];
|
|
5717
6657
|
const out = [];
|
|
5718
|
-
const entries = await
|
|
6658
|
+
const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
5719
6659
|
for (const entry2 of entries) {
|
|
5720
6660
|
if (entry2.isDirectory()) {
|
|
5721
6661
|
if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
|
|
5722
|
-
const child =
|
|
6662
|
+
const child = import_node_path34.default.join(start, entry2.name);
|
|
5723
6663
|
if (await isPythonVenvDir(child)) continue;
|
|
5724
6664
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
5725
6665
|
} else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
|
|
5726
|
-
out.push(
|
|
6666
|
+
out.push(import_node_path34.default.join(start, entry2.name));
|
|
5727
6667
|
}
|
|
5728
6668
|
}
|
|
5729
6669
|
return out;
|
|
@@ -5742,7 +6682,7 @@ function blockBody(content, from) {
|
|
|
5742
6682
|
}
|
|
5743
6683
|
return null;
|
|
5744
6684
|
}
|
|
5745
|
-
function
|
|
6685
|
+
function lineAt2(content, index) {
|
|
5746
6686
|
let line = 1;
|
|
5747
6687
|
for (let i = 0; i < index && i < content.length; i++) {
|
|
5748
6688
|
if (content[i] === "\n") line++;
|
|
@@ -5754,8 +6694,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5754
6694
|
let edgesAdded = 0;
|
|
5755
6695
|
const files = await walkTfFiles(scanPath);
|
|
5756
6696
|
for (const file of files) {
|
|
5757
|
-
const content = await
|
|
5758
|
-
const evidenceFile = toPosix2(
|
|
6697
|
+
const content = await import_node_fs18.promises.readFile(file, "utf8");
|
|
6698
|
+
const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
|
|
5759
6699
|
const resources = [];
|
|
5760
6700
|
const byKey = /* @__PURE__ */ new Map();
|
|
5761
6701
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -5790,16 +6730,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5790
6730
|
if (!target) continue;
|
|
5791
6731
|
if (seen.has(target.nodeId)) continue;
|
|
5792
6732
|
seen.add(target.nodeId);
|
|
5793
|
-
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
6733
|
+
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
|
|
5794
6734
|
if (graph.hasEdge(edgeId)) continue;
|
|
5795
|
-
const line =
|
|
6735
|
+
const line = lineAt2(content, resource.bodyOffset + ref.index);
|
|
5796
6736
|
const edge = {
|
|
5797
6737
|
id: edgeId,
|
|
5798
6738
|
source: resource.nodeId,
|
|
5799
6739
|
target: target.nodeId,
|
|
5800
|
-
type:
|
|
5801
|
-
provenance:
|
|
5802
|
-
confidence: (0,
|
|
6740
|
+
type: import_types24.EdgeType.DEPENDS_ON,
|
|
6741
|
+
provenance: import_types24.Provenance.EXTRACTED,
|
|
6742
|
+
confidence: (0, import_types24.confidenceForExtracted)("structural"),
|
|
5803
6743
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
5804
6744
|
};
|
|
5805
6745
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -5812,8 +6752,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5812
6752
|
|
|
5813
6753
|
// src/extract/infra/k8s.ts
|
|
5814
6754
|
init_cjs_shims();
|
|
5815
|
-
var
|
|
5816
|
-
var
|
|
6755
|
+
var import_node_fs19 = require("fs");
|
|
6756
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5817
6757
|
var import_yaml3 = require("yaml");
|
|
5818
6758
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
5819
6759
|
Service: "k8s-service",
|
|
@@ -5827,15 +6767,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
5827
6767
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
5828
6768
|
if (depth > max) return [];
|
|
5829
6769
|
const out = [];
|
|
5830
|
-
const entries = await
|
|
6770
|
+
const entries = await import_node_fs19.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
5831
6771
|
for (const entry2 of entries) {
|
|
5832
6772
|
if (entry2.isDirectory()) {
|
|
5833
6773
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
5834
|
-
const child =
|
|
6774
|
+
const child = import_node_path35.default.join(start, entry2.name);
|
|
5835
6775
|
if (await isPythonVenvDir(child)) continue;
|
|
5836
6776
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
5837
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
5838
|
-
out.push(
|
|
6777
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry2.name))) {
|
|
6778
|
+
out.push(import_node_path35.default.join(start, entry2.name));
|
|
5839
6779
|
}
|
|
5840
6780
|
}
|
|
5841
6781
|
return out;
|
|
@@ -5844,7 +6784,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
5844
6784
|
let nodesAdded = 0;
|
|
5845
6785
|
const files = await walkYamlFiles2(scanPath);
|
|
5846
6786
|
for (const file of files) {
|
|
5847
|
-
const content = await
|
|
6787
|
+
const content = await import_node_fs19.promises.readFile(file, "utf8");
|
|
5848
6788
|
let docs;
|
|
5849
6789
|
try {
|
|
5850
6790
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -5879,17 +6819,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
5879
6819
|
}
|
|
5880
6820
|
|
|
5881
6821
|
// src/extract/index.ts
|
|
5882
|
-
var
|
|
6822
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
5883
6823
|
|
|
5884
6824
|
// src/extract/retire.ts
|
|
5885
6825
|
init_cjs_shims();
|
|
5886
|
-
var
|
|
5887
|
-
var
|
|
5888
|
-
var
|
|
6826
|
+
var import_node_fs20 = require("fs");
|
|
6827
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
6828
|
+
var import_types25 = require("@neat.is/types");
|
|
5889
6829
|
function dropOrphanedFileNodes(graph) {
|
|
5890
6830
|
const orphans = [];
|
|
5891
6831
|
graph.forEachNode((id, attrs) => {
|
|
5892
|
-
if (attrs.type !==
|
|
6832
|
+
if (attrs.type !== import_types25.NodeType.FileNode) return;
|
|
5893
6833
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
5894
6834
|
orphans.push(id);
|
|
5895
6835
|
}
|
|
@@ -5902,7 +6842,7 @@ function retireEdgesByFile(graph, file) {
|
|
|
5902
6842
|
const toDrop = [];
|
|
5903
6843
|
graph.forEachEdge((id, attrs) => {
|
|
5904
6844
|
const edge = attrs;
|
|
5905
|
-
if (edge.provenance !==
|
|
6845
|
+
if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
|
|
5906
6846
|
if (!edge.evidence?.file) return;
|
|
5907
6847
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
5908
6848
|
});
|
|
@@ -5915,14 +6855,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
5915
6855
|
const bases = [scanPath, ...serviceDirs];
|
|
5916
6856
|
graph.forEachEdge((id, attrs) => {
|
|
5917
6857
|
const edge = attrs;
|
|
5918
|
-
if (edge.provenance !==
|
|
6858
|
+
if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
|
|
5919
6859
|
const evidenceFile = edge.evidence?.file;
|
|
5920
6860
|
if (!evidenceFile) return;
|
|
5921
|
-
if (
|
|
5922
|
-
if (!(0,
|
|
6861
|
+
if (import_node_path36.default.isAbsolute(evidenceFile)) {
|
|
6862
|
+
if (!(0, import_node_fs20.existsSync)(evidenceFile)) toDrop.push(id);
|
|
5923
6863
|
return;
|
|
5924
6864
|
}
|
|
5925
|
-
const found = bases.some((base) => (0,
|
|
6865
|
+
const found = bases.some((base) => (0, import_node_fs20.existsSync)(import_node_path36.default.join(base, evidenceFile)));
|
|
5926
6866
|
if (!found) toDrop.push(id);
|
|
5927
6867
|
});
|
|
5928
6868
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -5941,6 +6881,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5941
6881
|
const importGraph = await addImports(graph, services);
|
|
5942
6882
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
5943
6883
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
6884
|
+
const routePhase = await addRoutes(graph, services);
|
|
6885
|
+
const grpcPhase = await addGrpcMethods(graph, services);
|
|
5944
6886
|
const phase4 = await addCallEdges(graph, services);
|
|
5945
6887
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
5946
6888
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
@@ -5962,7 +6904,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5962
6904
|
}
|
|
5963
6905
|
const droppedEntries = drainDroppedExtracted();
|
|
5964
6906
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
5965
|
-
const rejectedPath =
|
|
6907
|
+
const rejectedPath = import_node_path37.default.join(import_node_path37.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
5966
6908
|
try {
|
|
5967
6909
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
5968
6910
|
} catch (err) {
|
|
@@ -5972,8 +6914,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5972
6914
|
}
|
|
5973
6915
|
}
|
|
5974
6916
|
const result = {
|
|
5975
|
-
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
5976
|
-
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
6917
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
6918
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
5977
6919
|
frontiersPromoted,
|
|
5978
6920
|
extractionErrors: errorEntries.length,
|
|
5979
6921
|
errorEntries,
|
|
@@ -5996,7 +6938,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5996
6938
|
|
|
5997
6939
|
// src/divergences.ts
|
|
5998
6940
|
init_cjs_shims();
|
|
5999
|
-
var
|
|
6941
|
+
var import_types26 = require("@neat.is/types");
|
|
6000
6942
|
function bucketKey(source, target, type) {
|
|
6001
6943
|
return `${type}|${source}|${target}`;
|
|
6002
6944
|
}
|
|
@@ -6004,22 +6946,22 @@ function bucketEdges(graph) {
|
|
|
6004
6946
|
const buckets = /* @__PURE__ */ new Map();
|
|
6005
6947
|
graph.forEachEdge((id, attrs) => {
|
|
6006
6948
|
const e = attrs;
|
|
6007
|
-
const parsed = (0,
|
|
6949
|
+
const parsed = (0, import_types26.parseEdgeId)(id);
|
|
6008
6950
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
6009
6951
|
const key = bucketKey(e.source, e.target, e.type);
|
|
6010
6952
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
6011
6953
|
switch (provenance) {
|
|
6012
|
-
case
|
|
6954
|
+
case import_types26.Provenance.EXTRACTED:
|
|
6013
6955
|
cur.extracted = e;
|
|
6014
6956
|
break;
|
|
6015
|
-
case
|
|
6957
|
+
case import_types26.Provenance.OBSERVED:
|
|
6016
6958
|
cur.observed = e;
|
|
6017
6959
|
break;
|
|
6018
|
-
case
|
|
6960
|
+
case import_types26.Provenance.INFERRED:
|
|
6019
6961
|
cur.inferred = e;
|
|
6020
6962
|
break;
|
|
6021
6963
|
default:
|
|
6022
|
-
if (e.provenance ===
|
|
6964
|
+
if (e.provenance === import_types26.Provenance.STALE) cur.stale = e;
|
|
6023
6965
|
}
|
|
6024
6966
|
buckets.set(key, cur);
|
|
6025
6967
|
});
|
|
@@ -6028,7 +6970,12 @@ function bucketEdges(graph) {
|
|
|
6028
6970
|
function nodeIsFrontier(graph, nodeId) {
|
|
6029
6971
|
if (!graph.hasNode(nodeId)) return false;
|
|
6030
6972
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
6031
|
-
return attrs.type ===
|
|
6973
|
+
return attrs.type === import_types26.NodeType.FrontierNode;
|
|
6974
|
+
}
|
|
6975
|
+
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
6976
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
6977
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
6978
|
+
return attrs.type === import_types26.NodeType.WebSocketChannelNode;
|
|
6032
6979
|
}
|
|
6033
6980
|
function clampConfidence(n) {
|
|
6034
6981
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -6048,14 +6995,14 @@ function gradedConfidence(edge) {
|
|
|
6048
6995
|
return clampConfidence(confidenceForEdge(edge));
|
|
6049
6996
|
}
|
|
6050
6997
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6998
|
+
import_types26.EdgeType.CALLS,
|
|
6999
|
+
import_types26.EdgeType.CONNECTS_TO,
|
|
7000
|
+
import_types26.EdgeType.PUBLISHES_TO,
|
|
7001
|
+
import_types26.EdgeType.CONSUMES_FROM
|
|
6055
7002
|
]);
|
|
6056
7003
|
function detectMissingDivergences(graph, bucket) {
|
|
6057
7004
|
const out = [];
|
|
6058
|
-
if (bucket.type ===
|
|
7005
|
+
if (bucket.type === import_types26.EdgeType.CONTAINS) return out;
|
|
6059
7006
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
6060
7007
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
6061
7008
|
out.push({
|
|
@@ -6070,7 +7017,7 @@ function detectMissingDivergences(graph, bucket) {
|
|
|
6070
7017
|
});
|
|
6071
7018
|
}
|
|
6072
7019
|
}
|
|
6073
|
-
if (bucket.observed && !bucket.extracted) {
|
|
7020
|
+
if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {
|
|
6074
7021
|
out.push({
|
|
6075
7022
|
type: "missing-extracted",
|
|
6076
7023
|
source: bucket.source,
|
|
@@ -6096,7 +7043,7 @@ function declaredHostFor(svc) {
|
|
|
6096
7043
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
6097
7044
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6098
7045
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6099
|
-
if (e.type ===
|
|
7046
|
+
if (e.type === import_types26.EdgeType.CONFIGURED_BY && e.provenance === import_types26.Provenance.EXTRACTED) {
|
|
6100
7047
|
return true;
|
|
6101
7048
|
}
|
|
6102
7049
|
}
|
|
@@ -6109,10 +7056,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
6109
7056
|
const out = [];
|
|
6110
7057
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6111
7058
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6112
|
-
if (edge.type !==
|
|
6113
|
-
if (edge.provenance !==
|
|
7059
|
+
if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
|
|
7060
|
+
if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
|
|
6114
7061
|
const target = graph.getNodeAttributes(edge.target);
|
|
6115
|
-
if (target.type !==
|
|
7062
|
+
if (target.type !== import_types26.NodeType.DatabaseNode) continue;
|
|
6116
7063
|
const observedHost = target.host?.trim();
|
|
6117
7064
|
if (!observedHost) continue;
|
|
6118
7065
|
if (observedHost === declaredHost) continue;
|
|
@@ -6134,10 +7081,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
6134
7081
|
const deps = svc.dependencies ?? {};
|
|
6135
7082
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6136
7083
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6137
|
-
if (edge.type !==
|
|
6138
|
-
if (edge.provenance !==
|
|
7084
|
+
if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
|
|
7085
|
+
if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
|
|
6139
7086
|
const target = graph.getNodeAttributes(edge.target);
|
|
6140
|
-
if (target.type !==
|
|
7087
|
+
if (target.type !== import_types26.NodeType.DatabaseNode) continue;
|
|
6141
7088
|
for (const pair of compatPairs()) {
|
|
6142
7089
|
if (pair.engine !== target.engine) continue;
|
|
6143
7090
|
const declared = deps[pair.driver];
|
|
@@ -6196,7 +7143,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
6196
7143
|
for (const d of all) {
|
|
6197
7144
|
if (d.type !== "host-mismatch") continue;
|
|
6198
7145
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
6199
|
-
declaredHalf.add((0,
|
|
7146
|
+
declaredHalf.add((0, import_types26.databaseId)(d.extractedHost));
|
|
6200
7147
|
}
|
|
6201
7148
|
if (observedHalf.size === 0) return all;
|
|
6202
7149
|
return all.filter((d) => {
|
|
@@ -6215,7 +7162,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6215
7162
|
}
|
|
6216
7163
|
graph.forEachNode((nodeId, attrs) => {
|
|
6217
7164
|
const n = attrs;
|
|
6218
|
-
if (n.type !==
|
|
7165
|
+
if (n.type !== import_types26.NodeType.ServiceNode) return;
|
|
6219
7166
|
const svc = n;
|
|
6220
7167
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
6221
7168
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -6249,7 +7196,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6249
7196
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
6250
7197
|
return a.target.localeCompare(b.target);
|
|
6251
7198
|
});
|
|
6252
|
-
return
|
|
7199
|
+
return import_types26.DivergenceResultSchema.parse({
|
|
6253
7200
|
divergences: filtered,
|
|
6254
7201
|
totalAffected: filtered.length,
|
|
6255
7202
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -6258,9 +7205,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6258
7205
|
|
|
6259
7206
|
// src/persist.ts
|
|
6260
7207
|
init_cjs_shims();
|
|
6261
|
-
var
|
|
6262
|
-
var
|
|
6263
|
-
var
|
|
7208
|
+
var import_node_fs21 = require("fs");
|
|
7209
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
7210
|
+
var import_types27 = require("@neat.is/types");
|
|
6264
7211
|
var SCHEMA_VERSION = 4;
|
|
6265
7212
|
function migrateV1ToV2(payload) {
|
|
6266
7213
|
const nodes = payload.graph.nodes;
|
|
@@ -6282,12 +7229,12 @@ function migrateV2ToV3(payload) {
|
|
|
6282
7229
|
for (const edge of edges) {
|
|
6283
7230
|
const attrs = edge.attributes;
|
|
6284
7231
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
6285
|
-
attrs.provenance =
|
|
7232
|
+
attrs.provenance = import_types27.Provenance.OBSERVED;
|
|
6286
7233
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
6287
7234
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
6288
7235
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
6289
7236
|
if (type && source && target) {
|
|
6290
|
-
const newId = (0,
|
|
7237
|
+
const newId = (0, import_types27.observedEdgeId)(source, target, type);
|
|
6291
7238
|
attrs.id = newId;
|
|
6292
7239
|
if (edge.key) edge.key = newId;
|
|
6293
7240
|
}
|
|
@@ -6296,7 +7243,7 @@ function migrateV2ToV3(payload) {
|
|
|
6296
7243
|
return { ...payload, schemaVersion: 3 };
|
|
6297
7244
|
}
|
|
6298
7245
|
async function ensureDir(filePath) {
|
|
6299
|
-
await
|
|
7246
|
+
await import_node_fs21.promises.mkdir(import_node_path38.default.dirname(filePath), { recursive: true });
|
|
6300
7247
|
}
|
|
6301
7248
|
async function saveGraphToDisk(graph, outPath) {
|
|
6302
7249
|
await ensureDir(outPath);
|
|
@@ -6306,13 +7253,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
6306
7253
|
graph: graph.export()
|
|
6307
7254
|
};
|
|
6308
7255
|
const tmp = `${outPath}.tmp`;
|
|
6309
|
-
await
|
|
6310
|
-
await
|
|
7256
|
+
await import_node_fs21.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
7257
|
+
await import_node_fs21.promises.rename(tmp, outPath);
|
|
6311
7258
|
}
|
|
6312
7259
|
async function loadGraphFromDisk(graph, outPath) {
|
|
6313
7260
|
let raw;
|
|
6314
7261
|
try {
|
|
6315
|
-
raw = await
|
|
7262
|
+
raw = await import_node_fs21.promises.readFile(outPath, "utf8");
|
|
6316
7263
|
} catch (err) {
|
|
6317
7264
|
if (err.code === "ENOENT") return;
|
|
6318
7265
|
throw err;
|
|
@@ -6377,8 +7324,8 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
6377
7324
|
|
|
6378
7325
|
// src/gitignore.ts
|
|
6379
7326
|
init_cjs_shims();
|
|
6380
|
-
var
|
|
6381
|
-
var
|
|
7327
|
+
var import_node_fs22 = require("fs");
|
|
7328
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
6382
7329
|
var NEAT_OUT_LINE = "neat-out/";
|
|
6383
7330
|
var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
|
|
6384
7331
|
function isNeatOutLine(line) {
|
|
@@ -6386,15 +7333,15 @@ function isNeatOutLine(line) {
|
|
|
6386
7333
|
return trimmed === "neat-out/" || trimmed === "neat-out";
|
|
6387
7334
|
}
|
|
6388
7335
|
async function ensureNeatOutIgnored(projectDir) {
|
|
6389
|
-
const file =
|
|
7336
|
+
const file = import_node_path39.default.join(projectDir, ".gitignore");
|
|
6390
7337
|
let existing = null;
|
|
6391
7338
|
try {
|
|
6392
|
-
existing = await
|
|
7339
|
+
existing = await import_node_fs22.promises.readFile(file, "utf8");
|
|
6393
7340
|
} catch (err) {
|
|
6394
7341
|
if (err.code !== "ENOENT") throw err;
|
|
6395
7342
|
}
|
|
6396
7343
|
if (existing === null) {
|
|
6397
|
-
await
|
|
7344
|
+
await import_node_fs22.promises.writeFile(file, `${NEAT_HEADER}
|
|
6398
7345
|
${NEAT_OUT_LINE}
|
|
6399
7346
|
`, "utf8");
|
|
6400
7347
|
return { action: "created", file };
|
|
@@ -6407,13 +7354,13 @@ ${NEAT_OUT_LINE}
|
|
|
6407
7354
|
${NEAT_HEADER}
|
|
6408
7355
|
${NEAT_OUT_LINE}
|
|
6409
7356
|
`;
|
|
6410
|
-
await
|
|
7357
|
+
await import_node_fs22.promises.writeFile(file, existing + appended, "utf8");
|
|
6411
7358
|
return { action: "added", file };
|
|
6412
7359
|
}
|
|
6413
7360
|
|
|
6414
7361
|
// src/summary.ts
|
|
6415
7362
|
init_cjs_shims();
|
|
6416
|
-
var
|
|
7363
|
+
var import_types28 = require("@neat.is/types");
|
|
6417
7364
|
function renderOtelEnvBlock() {
|
|
6418
7365
|
return [
|
|
6419
7366
|
"for prod OTel routing, set these in your deploy platform's env:",
|
|
@@ -6423,19 +7370,19 @@ function renderOtelEnvBlock() {
|
|
|
6423
7370
|
}
|
|
6424
7371
|
function findIncompatServices(nodes) {
|
|
6425
7372
|
return nodes.filter(
|
|
6426
|
-
(n) => n.type ===
|
|
7373
|
+
(n) => n.type === import_types28.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
|
|
6427
7374
|
);
|
|
6428
7375
|
}
|
|
6429
7376
|
function servicesWithoutObserved(nodes, edges) {
|
|
6430
7377
|
const seen = /* @__PURE__ */ new Set();
|
|
6431
7378
|
for (const e of edges) {
|
|
6432
|
-
if (e.provenance ===
|
|
7379
|
+
if (e.provenance === import_types28.Provenance.OBSERVED) {
|
|
6433
7380
|
seen.add(e.source);
|
|
6434
7381
|
seen.add(e.target);
|
|
6435
7382
|
}
|
|
6436
7383
|
}
|
|
6437
7384
|
return nodes.filter(
|
|
6438
|
-
(n) => n.type ===
|
|
7385
|
+
(n) => n.type === import_types28.NodeType.ServiceNode && !seen.has(n.id)
|
|
6439
7386
|
);
|
|
6440
7387
|
}
|
|
6441
7388
|
function formatDivergence(d) {
|
|
@@ -6509,27 +7456,27 @@ function formatIncompat(inc) {
|
|
|
6509
7456
|
|
|
6510
7457
|
// src/watch.ts
|
|
6511
7458
|
init_cjs_shims();
|
|
6512
|
-
var
|
|
6513
|
-
var
|
|
7459
|
+
var import_node_fs30 = __toESM(require("fs"), 1);
|
|
7460
|
+
var import_node_path49 = __toESM(require("path"), 1);
|
|
6514
7461
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
6515
7462
|
|
|
6516
7463
|
// src/api.ts
|
|
6517
7464
|
init_cjs_shims();
|
|
6518
7465
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
6519
7466
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
6520
|
-
var
|
|
7467
|
+
var import_types30 = require("@neat.is/types");
|
|
6521
7468
|
|
|
6522
7469
|
// src/extend/index.ts
|
|
6523
7470
|
init_cjs_shims();
|
|
6524
|
-
var
|
|
6525
|
-
var
|
|
7471
|
+
var import_node_fs24 = require("fs");
|
|
7472
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
6526
7473
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
6527
7474
|
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
6528
7475
|
|
|
6529
7476
|
// src/installers/package-manager.ts
|
|
6530
7477
|
init_cjs_shims();
|
|
6531
|
-
var
|
|
6532
|
-
var
|
|
7478
|
+
var import_node_fs23 = require("fs");
|
|
7479
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
6533
7480
|
var import_node_child_process = require("child_process");
|
|
6534
7481
|
var LOCKFILE_PRIORITY = [
|
|
6535
7482
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -6544,29 +7491,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
6544
7491
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
6545
7492
|
async function exists2(p) {
|
|
6546
7493
|
try {
|
|
6547
|
-
await
|
|
7494
|
+
await import_node_fs23.promises.access(p);
|
|
6548
7495
|
return true;
|
|
6549
7496
|
} catch {
|
|
6550
7497
|
return false;
|
|
6551
7498
|
}
|
|
6552
7499
|
}
|
|
6553
7500
|
async function detectPackageManager(serviceDir) {
|
|
6554
|
-
let dir =
|
|
7501
|
+
let dir = import_node_path40.default.resolve(serviceDir);
|
|
6555
7502
|
const stops = /* @__PURE__ */ new Set();
|
|
6556
7503
|
for (let i = 0; i < 64; i++) {
|
|
6557
7504
|
if (stops.has(dir)) break;
|
|
6558
7505
|
stops.add(dir);
|
|
6559
7506
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
6560
|
-
const lockPath =
|
|
7507
|
+
const lockPath = import_node_path40.default.join(dir, candidate.lockfile);
|
|
6561
7508
|
if (await exists2(lockPath)) {
|
|
6562
7509
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
6563
7510
|
}
|
|
6564
7511
|
}
|
|
6565
|
-
const parent =
|
|
7512
|
+
const parent = import_node_path40.default.dirname(dir);
|
|
6566
7513
|
if (parent === dir) break;
|
|
6567
7514
|
dir = parent;
|
|
6568
7515
|
}
|
|
6569
|
-
return { pm: "npm", cwd:
|
|
7516
|
+
return { pm: "npm", cwd: import_node_path40.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
6570
7517
|
}
|
|
6571
7518
|
async function runPackageManagerInstall(cmd) {
|
|
6572
7519
|
return new Promise((resolve) => {
|
|
@@ -6608,30 +7555,30 @@ ${err.message}`
|
|
|
6608
7555
|
// src/extend/index.ts
|
|
6609
7556
|
async function fileExists2(p) {
|
|
6610
7557
|
try {
|
|
6611
|
-
await
|
|
7558
|
+
await import_node_fs24.promises.access(p);
|
|
6612
7559
|
return true;
|
|
6613
7560
|
} catch {
|
|
6614
7561
|
return false;
|
|
6615
7562
|
}
|
|
6616
7563
|
}
|
|
6617
7564
|
async function readPackageJson(scanPath) {
|
|
6618
|
-
const pkgPath =
|
|
6619
|
-
const raw = await
|
|
7565
|
+
const pkgPath = import_node_path41.default.join(scanPath, "package.json");
|
|
7566
|
+
const raw = await import_node_fs24.promises.readFile(pkgPath, "utf8");
|
|
6620
7567
|
return JSON.parse(raw);
|
|
6621
7568
|
}
|
|
6622
7569
|
async function findHookFiles(scanPath) {
|
|
6623
|
-
const entries = await
|
|
7570
|
+
const entries = await import_node_fs24.promises.readdir(scanPath);
|
|
6624
7571
|
return entries.filter(
|
|
6625
7572
|
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
6626
7573
|
).sort();
|
|
6627
7574
|
}
|
|
6628
7575
|
function extendLogPath() {
|
|
6629
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
7576
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path41.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
6630
7577
|
}
|
|
6631
7578
|
async function appendExtendLog(entry2) {
|
|
6632
7579
|
const logPath = extendLogPath();
|
|
6633
|
-
await
|
|
6634
|
-
await
|
|
7580
|
+
await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(logPath), { recursive: true });
|
|
7581
|
+
await import_node_fs24.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
|
|
6635
7582
|
}
|
|
6636
7583
|
function splicedContent(fileContent, snippet2) {
|
|
6637
7584
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -6689,7 +7636,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
6689
7636
|
}
|
|
6690
7637
|
async function describeProjectInstrumentation(ctx) {
|
|
6691
7638
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
6692
|
-
const envNeat = await fileExists2(
|
|
7639
|
+
const envNeat = await fileExists2(import_node_path41.default.join(ctx.scanPath, ".env.neat"));
|
|
6693
7640
|
const registryInstrPackages = new Set(
|
|
6694
7641
|
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
6695
7642
|
);
|
|
@@ -6711,31 +7658,31 @@ async function applyExtension(ctx, args, options) {
|
|
|
6711
7658
|
);
|
|
6712
7659
|
}
|
|
6713
7660
|
for (const file of hookFiles) {
|
|
6714
|
-
const content = await
|
|
7661
|
+
const content = await import_node_fs24.promises.readFile(import_node_path41.default.join(ctx.scanPath, file), "utf8");
|
|
6715
7662
|
if (content.includes(args.registration_snippet)) {
|
|
6716
7663
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
6717
7664
|
}
|
|
6718
7665
|
}
|
|
6719
7666
|
const primaryFile = hookFiles[0];
|
|
6720
|
-
const primaryPath =
|
|
7667
|
+
const primaryPath = import_node_path41.default.join(ctx.scanPath, primaryFile);
|
|
6721
7668
|
const filesTouched = [];
|
|
6722
7669
|
const depsAdded = [];
|
|
6723
|
-
const pkgPath =
|
|
7670
|
+
const pkgPath = import_node_path41.default.join(ctx.scanPath, "package.json");
|
|
6724
7671
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
6725
7672
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
6726
7673
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
6727
|
-
await
|
|
7674
|
+
await import_node_fs24.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
6728
7675
|
filesTouched.push("package.json");
|
|
6729
7676
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
6730
7677
|
}
|
|
6731
|
-
const hookContent = await
|
|
7678
|
+
const hookContent = await import_node_fs24.promises.readFile(primaryPath, "utf8");
|
|
6732
7679
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
6733
7680
|
if (!patched) {
|
|
6734
7681
|
throw new Error(
|
|
6735
7682
|
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
6736
7683
|
);
|
|
6737
7684
|
}
|
|
6738
|
-
await
|
|
7685
|
+
await import_node_fs24.promises.writeFile(primaryPath, patched, "utf8");
|
|
6739
7686
|
filesTouched.push(primaryFile);
|
|
6740
7687
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
6741
7688
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -6766,7 +7713,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
6766
7713
|
};
|
|
6767
7714
|
}
|
|
6768
7715
|
for (const file of hookFiles) {
|
|
6769
|
-
const content = await
|
|
7716
|
+
const content = await import_node_fs24.promises.readFile(import_node_path41.default.join(ctx.scanPath, file), "utf8");
|
|
6770
7717
|
if (content.includes(args.registration_snippet)) {
|
|
6771
7718
|
return {
|
|
6772
7719
|
library: args.library,
|
|
@@ -6788,7 +7735,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
6788
7735
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
6789
7736
|
filesTouched.push("package.json");
|
|
6790
7737
|
}
|
|
6791
|
-
const hookContent = await
|
|
7738
|
+
const hookContent = await import_node_fs24.promises.readFile(import_node_path41.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
6792
7739
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
6793
7740
|
if (patched) {
|
|
6794
7741
|
filesTouched.push(primaryFile);
|
|
@@ -6803,28 +7750,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
6803
7750
|
if (!await fileExists2(logPath)) {
|
|
6804
7751
|
return { undone: false, message: "no apply found for library" };
|
|
6805
7752
|
}
|
|
6806
|
-
const raw = await
|
|
7753
|
+
const raw = await import_node_fs24.promises.readFile(logPath, "utf8");
|
|
6807
7754
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
6808
7755
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
6809
7756
|
if (!match) {
|
|
6810
7757
|
return { undone: false, message: "no apply found for library" };
|
|
6811
7758
|
}
|
|
6812
|
-
const pkgPath =
|
|
7759
|
+
const pkgPath = import_node_path41.default.join(ctx.scanPath, "package.json");
|
|
6813
7760
|
if (await fileExists2(pkgPath)) {
|
|
6814
7761
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
6815
7762
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
6816
7763
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
6817
7764
|
pkg.dependencies = rest;
|
|
6818
|
-
await
|
|
7765
|
+
await import_node_fs24.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
6819
7766
|
}
|
|
6820
7767
|
}
|
|
6821
7768
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
6822
7769
|
for (const file of hookFiles) {
|
|
6823
|
-
const filePath =
|
|
6824
|
-
const content = await
|
|
7770
|
+
const filePath = import_node_path41.default.join(ctx.scanPath, file);
|
|
7771
|
+
const content = await import_node_fs24.promises.readFile(filePath, "utf8");
|
|
6825
7772
|
if (content.includes(match.registration_snippet)) {
|
|
6826
7773
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
6827
|
-
await
|
|
7774
|
+
await import_node_fs24.promises.writeFile(filePath, filtered, "utf8");
|
|
6828
7775
|
break;
|
|
6829
7776
|
}
|
|
6830
7777
|
}
|
|
@@ -6836,7 +7783,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
6836
7783
|
|
|
6837
7784
|
// src/diff.ts
|
|
6838
7785
|
init_cjs_shims();
|
|
6839
|
-
var
|
|
7786
|
+
var import_node_fs25 = require("fs");
|
|
6840
7787
|
async function loadSnapshotForDiff(target) {
|
|
6841
7788
|
if (/^https?:\/\//i.test(target)) {
|
|
6842
7789
|
const res = await fetch(target);
|
|
@@ -6845,7 +7792,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
6845
7792
|
}
|
|
6846
7793
|
return await res.json();
|
|
6847
7794
|
}
|
|
6848
|
-
const raw = await
|
|
7795
|
+
const raw = await import_node_fs25.promises.readFile(target, "utf8");
|
|
6849
7796
|
return JSON.parse(raw);
|
|
6850
7797
|
}
|
|
6851
7798
|
function indexEntries(entries) {
|
|
@@ -6913,23 +7860,23 @@ function canonicalJson(value) {
|
|
|
6913
7860
|
|
|
6914
7861
|
// src/projects.ts
|
|
6915
7862
|
init_cjs_shims();
|
|
6916
|
-
var
|
|
7863
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
6917
7864
|
function pathsForProject(project, baseDir) {
|
|
6918
7865
|
if (project === DEFAULT_PROJECT) {
|
|
6919
7866
|
return {
|
|
6920
|
-
snapshotPath:
|
|
6921
|
-
errorsPath:
|
|
6922
|
-
staleEventsPath:
|
|
6923
|
-
embeddingsCachePath:
|
|
6924
|
-
policyViolationsPath:
|
|
7867
|
+
snapshotPath: import_node_path42.default.join(baseDir, "graph.json"),
|
|
7868
|
+
errorsPath: import_node_path42.default.join(baseDir, "errors.ndjson"),
|
|
7869
|
+
staleEventsPath: import_node_path42.default.join(baseDir, "stale-events.ndjson"),
|
|
7870
|
+
embeddingsCachePath: import_node_path42.default.join(baseDir, "embeddings.json"),
|
|
7871
|
+
policyViolationsPath: import_node_path42.default.join(baseDir, "policy-violations.ndjson")
|
|
6925
7872
|
};
|
|
6926
7873
|
}
|
|
6927
7874
|
return {
|
|
6928
|
-
snapshotPath:
|
|
6929
|
-
errorsPath:
|
|
6930
|
-
staleEventsPath:
|
|
6931
|
-
embeddingsCachePath:
|
|
6932
|
-
policyViolationsPath:
|
|
7875
|
+
snapshotPath: import_node_path42.default.join(baseDir, `${project}.json`),
|
|
7876
|
+
errorsPath: import_node_path42.default.join(baseDir, `errors.${project}.ndjson`),
|
|
7877
|
+
staleEventsPath: import_node_path42.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
7878
|
+
embeddingsCachePath: import_node_path42.default.join(baseDir, `embeddings.${project}.json`),
|
|
7879
|
+
policyViolationsPath: import_node_path42.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
6933
7880
|
};
|
|
6934
7881
|
}
|
|
6935
7882
|
var Projects = class {
|
|
@@ -6965,28 +7912,28 @@ var Projects = class {
|
|
|
6965
7912
|
|
|
6966
7913
|
// src/registry.ts
|
|
6967
7914
|
init_cjs_shims();
|
|
6968
|
-
var
|
|
7915
|
+
var import_node_fs26 = require("fs");
|
|
6969
7916
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
6970
|
-
var
|
|
6971
|
-
var
|
|
7917
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
7918
|
+
var import_types29 = require("@neat.is/types");
|
|
6972
7919
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
6973
7920
|
var LOCK_RETRY_MS = 50;
|
|
6974
7921
|
function neatHome() {
|
|
6975
7922
|
const override = process.env.NEAT_HOME;
|
|
6976
|
-
if (override && override.length > 0) return
|
|
6977
|
-
return
|
|
7923
|
+
if (override && override.length > 0) return import_node_path43.default.resolve(override);
|
|
7924
|
+
return import_node_path43.default.join(import_node_os3.default.homedir(), ".neat");
|
|
6978
7925
|
}
|
|
6979
7926
|
function registryPath() {
|
|
6980
|
-
return
|
|
7927
|
+
return import_node_path43.default.join(neatHome(), "projects.json");
|
|
6981
7928
|
}
|
|
6982
7929
|
function registryLockPath() {
|
|
6983
|
-
return
|
|
7930
|
+
return import_node_path43.default.join(neatHome(), "projects.json.lock");
|
|
6984
7931
|
}
|
|
6985
7932
|
function daemonPidPath() {
|
|
6986
|
-
return
|
|
7933
|
+
return import_node_path43.default.join(neatHome(), "neatd.pid");
|
|
6987
7934
|
}
|
|
6988
7935
|
function daemonsDir() {
|
|
6989
|
-
return
|
|
7936
|
+
return import_node_path43.default.join(neatHome(), "daemons");
|
|
6990
7937
|
}
|
|
6991
7938
|
function isFiniteInt(v) {
|
|
6992
7939
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -7019,7 +7966,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
7019
7966
|
const dir = daemonsDir();
|
|
7020
7967
|
let names;
|
|
7021
7968
|
try {
|
|
7022
|
-
names = await
|
|
7969
|
+
names = await import_node_fs26.promises.readdir(dir);
|
|
7023
7970
|
} catch (err) {
|
|
7024
7971
|
if (err.code === "ENOENT") return [];
|
|
7025
7972
|
throw err;
|
|
@@ -7027,10 +7974,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
7027
7974
|
const out = [];
|
|
7028
7975
|
for (const name of names) {
|
|
7029
7976
|
if (!name.endsWith(".json")) continue;
|
|
7030
|
-
const file =
|
|
7977
|
+
const file = import_node_path43.default.join(dir, name);
|
|
7031
7978
|
let raw;
|
|
7032
7979
|
try {
|
|
7033
|
-
raw = await
|
|
7980
|
+
raw = await import_node_fs26.promises.readFile(file, "utf8");
|
|
7034
7981
|
} catch {
|
|
7035
7982
|
continue;
|
|
7036
7983
|
}
|
|
@@ -7043,7 +7990,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
7043
7990
|
return out;
|
|
7044
7991
|
}
|
|
7045
7992
|
async function removeDaemonRecord(source) {
|
|
7046
|
-
await
|
|
7993
|
+
await import_node_fs26.promises.unlink(source).catch(() => {
|
|
7047
7994
|
});
|
|
7048
7995
|
}
|
|
7049
7996
|
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
@@ -7100,7 +8047,7 @@ function isPidAliveDefault(pid) {
|
|
|
7100
8047
|
}
|
|
7101
8048
|
async function readPidFile(file) {
|
|
7102
8049
|
try {
|
|
7103
|
-
const raw = await
|
|
8050
|
+
const raw = await import_node_fs26.promises.readFile(file, "utf8");
|
|
7104
8051
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
7105
8052
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
7106
8053
|
} catch {
|
|
@@ -7148,32 +8095,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
7148
8095
|
}
|
|
7149
8096
|
}
|
|
7150
8097
|
async function normalizeProjectPath(input) {
|
|
7151
|
-
const resolved =
|
|
8098
|
+
const resolved = import_node_path43.default.resolve(input);
|
|
7152
8099
|
try {
|
|
7153
|
-
return await
|
|
8100
|
+
return await import_node_fs26.promises.realpath(resolved);
|
|
7154
8101
|
} catch {
|
|
7155
8102
|
return resolved;
|
|
7156
8103
|
}
|
|
7157
8104
|
}
|
|
7158
8105
|
async function writeAtomically(target, contents) {
|
|
7159
|
-
await
|
|
8106
|
+
await import_node_fs26.promises.mkdir(import_node_path43.default.dirname(target), { recursive: true });
|
|
7160
8107
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
7161
|
-
const fd = await
|
|
8108
|
+
const fd = await import_node_fs26.promises.open(tmp, "w");
|
|
7162
8109
|
try {
|
|
7163
8110
|
await fd.writeFile(contents, "utf8");
|
|
7164
8111
|
await fd.sync();
|
|
7165
8112
|
} finally {
|
|
7166
8113
|
await fd.close();
|
|
7167
8114
|
}
|
|
7168
|
-
await
|
|
8115
|
+
await import_node_fs26.promises.rename(tmp, target);
|
|
7169
8116
|
}
|
|
7170
8117
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
7171
8118
|
const deadline = Date.now() + timeoutMs;
|
|
7172
|
-
await
|
|
8119
|
+
await import_node_fs26.promises.mkdir(import_node_path43.default.dirname(lockPath), { recursive: true });
|
|
7173
8120
|
let probedHolder = false;
|
|
7174
8121
|
while (true) {
|
|
7175
8122
|
try {
|
|
7176
|
-
const fd = await
|
|
8123
|
+
const fd = await import_node_fs26.promises.open(lockPath, "wx");
|
|
7177
8124
|
try {
|
|
7178
8125
|
await fd.writeFile(`${process.pid}
|
|
7179
8126
|
`, "utf8");
|
|
@@ -7198,7 +8145,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
7198
8145
|
}
|
|
7199
8146
|
}
|
|
7200
8147
|
async function releaseLock(lockPath) {
|
|
7201
|
-
await
|
|
8148
|
+
await import_node_fs26.promises.unlink(lockPath).catch(() => {
|
|
7202
8149
|
});
|
|
7203
8150
|
}
|
|
7204
8151
|
async function withLock(fn) {
|
|
@@ -7214,7 +8161,7 @@ async function readRegistry() {
|
|
|
7214
8161
|
const file = registryPath();
|
|
7215
8162
|
let raw;
|
|
7216
8163
|
try {
|
|
7217
|
-
raw = await
|
|
8164
|
+
raw = await import_node_fs26.promises.readFile(file, "utf8");
|
|
7218
8165
|
} catch (err) {
|
|
7219
8166
|
if (err.code === "ENOENT") {
|
|
7220
8167
|
return { version: 1, projects: [] };
|
|
@@ -7222,10 +8169,10 @@ async function readRegistry() {
|
|
|
7222
8169
|
throw err;
|
|
7223
8170
|
}
|
|
7224
8171
|
const parsed = JSON.parse(raw);
|
|
7225
|
-
return
|
|
8172
|
+
return import_types29.RegistryFileSchema.parse(parsed);
|
|
7226
8173
|
}
|
|
7227
8174
|
async function writeRegistry(reg) {
|
|
7228
|
-
const validated =
|
|
8175
|
+
const validated = import_types29.RegistryFileSchema.parse(reg);
|
|
7229
8176
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
7230
8177
|
}
|
|
7231
8178
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -7310,7 +8257,7 @@ function pruneTtlMs() {
|
|
|
7310
8257
|
}
|
|
7311
8258
|
async function statPathStatus(p) {
|
|
7312
8259
|
try {
|
|
7313
|
-
const stat = await
|
|
8260
|
+
const stat = await import_node_fs26.promises.stat(p);
|
|
7314
8261
|
return stat.isDirectory() ? "present" : "unknown";
|
|
7315
8262
|
} catch (err) {
|
|
7316
8263
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -7557,11 +8504,11 @@ function registerRoutes(scope, ctx) {
|
|
|
7557
8504
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7558
8505
|
const parsed = [];
|
|
7559
8506
|
for (const c of candidates) {
|
|
7560
|
-
const r =
|
|
8507
|
+
const r = import_types30.DivergenceTypeSchema.safeParse(c);
|
|
7561
8508
|
if (!r.success) {
|
|
7562
8509
|
return reply.code(400).send({
|
|
7563
8510
|
error: `unknown divergence type "${c}"`,
|
|
7564
|
-
allowed:
|
|
8511
|
+
allowed: import_types30.DivergenceTypeSchema.options
|
|
7565
8512
|
});
|
|
7566
8513
|
}
|
|
7567
8514
|
parsed.push(r.data);
|
|
@@ -7780,7 +8727,7 @@ function registerRoutes(scope, ctx) {
|
|
|
7780
8727
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
7781
8728
|
let violations = await log.readAll();
|
|
7782
8729
|
if (req.query.severity) {
|
|
7783
|
-
const sev =
|
|
8730
|
+
const sev = import_types30.PolicySeveritySchema.safeParse(req.query.severity);
|
|
7784
8731
|
if (!sev.success) {
|
|
7785
8732
|
return reply.code(400).send({
|
|
7786
8733
|
error: "invalid severity",
|
|
@@ -7819,7 +8766,7 @@ function registerRoutes(scope, ctx) {
|
|
|
7819
8766
|
scope.post("/policies/check", async (req, reply) => {
|
|
7820
8767
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
7821
8768
|
if (!proj) return;
|
|
7822
|
-
const parsed =
|
|
8769
|
+
const parsed = import_types30.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
7823
8770
|
if (!parsed.success) {
|
|
7824
8771
|
return reply.code(400).send({
|
|
7825
8772
|
error: "invalid /policies/check body",
|
|
@@ -8081,41 +9028,46 @@ init_otel();
|
|
|
8081
9028
|
|
|
8082
9029
|
// src/daemon.ts
|
|
8083
9030
|
init_cjs_shims();
|
|
8084
|
-
var
|
|
8085
|
-
var
|
|
9031
|
+
var import_node_fs28 = require("fs");
|
|
9032
|
+
var import_node_path47 = __toESM(require("path"), 1);
|
|
8086
9033
|
var import_node_module = require("module");
|
|
8087
9034
|
init_otel();
|
|
9035
|
+
|
|
9036
|
+
// src/connectors/index.ts
|
|
9037
|
+
init_cjs_shims();
|
|
9038
|
+
|
|
9039
|
+
// src/daemon.ts
|
|
8088
9040
|
init_auth();
|
|
8089
9041
|
|
|
8090
9042
|
// src/unrouted.ts
|
|
8091
9043
|
init_cjs_shims();
|
|
8092
|
-
var
|
|
8093
|
-
var
|
|
9044
|
+
var import_node_fs27 = require("fs");
|
|
9045
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
8094
9046
|
|
|
8095
9047
|
// src/daemon.ts
|
|
8096
|
-
var
|
|
9048
|
+
var import_types31 = require("@neat.is/types");
|
|
8097
9049
|
function daemonJsonPath(scanPath) {
|
|
8098
|
-
return
|
|
9050
|
+
return import_node_path47.default.join(scanPath, "neat-out", "daemon.json");
|
|
8099
9051
|
}
|
|
8100
9052
|
function daemonsDiscoveryDir(home) {
|
|
8101
9053
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
8102
|
-
return
|
|
9054
|
+
return import_node_path47.default.join(base, "daemons");
|
|
8103
9055
|
}
|
|
8104
9056
|
function daemonDiscoveryPath(project, home) {
|
|
8105
|
-
return
|
|
9057
|
+
return import_node_path47.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
8106
9058
|
}
|
|
8107
9059
|
function sanitizeDiscoveryName(project) {
|
|
8108
9060
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
8109
9061
|
}
|
|
8110
9062
|
function neatHomeFromEnv() {
|
|
8111
9063
|
const env = process.env.NEAT_HOME;
|
|
8112
|
-
if (env && env.length > 0) return
|
|
9064
|
+
if (env && env.length > 0) return import_node_path47.default.resolve(env);
|
|
8113
9065
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
8114
|
-
return
|
|
9066
|
+
return import_node_path47.default.join(home, ".neat");
|
|
8115
9067
|
}
|
|
8116
9068
|
async function readDaemonRecord(scanPath) {
|
|
8117
9069
|
try {
|
|
8118
|
-
const raw = await
|
|
9070
|
+
const raw = await import_node_fs28.promises.readFile(daemonJsonPath(scanPath), "utf8");
|
|
8119
9071
|
const parsed = JSON.parse(raw);
|
|
8120
9072
|
if (typeof parsed.project === "string" && parsed.ports && typeof parsed.ports.rest === "number" && typeof parsed.ports.otlp === "number" && typeof parsed.ports.web === "number") {
|
|
8121
9073
|
return parsed;
|
|
@@ -8155,7 +9107,7 @@ async function clearDaemonRecord(record, home) {
|
|
|
8155
9107
|
} catch {
|
|
8156
9108
|
}
|
|
8157
9109
|
try {
|
|
8158
|
-
await
|
|
9110
|
+
await import_node_fs28.promises.unlink(daemonDiscoveryPath(record.project, home));
|
|
8159
9111
|
} catch {
|
|
8160
9112
|
}
|
|
8161
9113
|
}
|
|
@@ -8181,8 +9133,8 @@ init_otel_grpc();
|
|
|
8181
9133
|
|
|
8182
9134
|
// src/search.ts
|
|
8183
9135
|
init_cjs_shims();
|
|
8184
|
-
var
|
|
8185
|
-
var
|
|
9136
|
+
var import_node_fs29 = require("fs");
|
|
9137
|
+
var import_node_path48 = __toESM(require("path"), 1);
|
|
8186
9138
|
var import_node_crypto3 = require("crypto");
|
|
8187
9139
|
var DEFAULT_LIMIT = 10;
|
|
8188
9140
|
var NOMIC_DIM = 768;
|
|
@@ -8217,6 +9169,30 @@ function embedText(node) {
|
|
|
8217
9169
|
if (filePath) parts.push(`path=${filePath}`);
|
|
8218
9170
|
break;
|
|
8219
9171
|
}
|
|
9172
|
+
case "RouteNode": {
|
|
9173
|
+
const method = node.method;
|
|
9174
|
+
const tmpl = node.pathTemplate;
|
|
9175
|
+
if (method) parts.push(`method=${method}`);
|
|
9176
|
+
if (tmpl) parts.push(`path=${tmpl}`);
|
|
9177
|
+
break;
|
|
9178
|
+
}
|
|
9179
|
+
case "GraphQLOperationNode": {
|
|
9180
|
+
const opType = node.operationType;
|
|
9181
|
+
if (opType) parts.push(`operationType=${opType}`);
|
|
9182
|
+
break;
|
|
9183
|
+
}
|
|
9184
|
+
case "GrpcMethodNode": {
|
|
9185
|
+
const rpcService = node.rpcService;
|
|
9186
|
+
const rpcMethod = node.rpcMethod;
|
|
9187
|
+
if (rpcService) parts.push(`rpcService=${rpcService}`);
|
|
9188
|
+
if (rpcMethod) parts.push(`rpcMethod=${rpcMethod}`);
|
|
9189
|
+
break;
|
|
9190
|
+
}
|
|
9191
|
+
case "WebSocketChannelNode": {
|
|
9192
|
+
const channel = node.channel;
|
|
9193
|
+
if (channel) parts.push(`channel=${channel}`);
|
|
9194
|
+
break;
|
|
9195
|
+
}
|
|
8220
9196
|
default:
|
|
8221
9197
|
break;
|
|
8222
9198
|
}
|
|
@@ -8312,7 +9288,7 @@ async function pickEmbedder() {
|
|
|
8312
9288
|
}
|
|
8313
9289
|
async function readCache(cachePath) {
|
|
8314
9290
|
try {
|
|
8315
|
-
const raw = await
|
|
9291
|
+
const raw = await import_node_fs29.promises.readFile(cachePath, "utf8");
|
|
8316
9292
|
const parsed = JSON.parse(raw);
|
|
8317
9293
|
if (parsed.version !== 1) return null;
|
|
8318
9294
|
return parsed;
|
|
@@ -8321,8 +9297,8 @@ async function readCache(cachePath) {
|
|
|
8321
9297
|
}
|
|
8322
9298
|
}
|
|
8323
9299
|
async function writeCache(cachePath, cache) {
|
|
8324
|
-
await
|
|
8325
|
-
await
|
|
9300
|
+
await import_node_fs29.promises.mkdir(import_node_path48.default.dirname(cachePath), { recursive: true });
|
|
9301
|
+
await import_node_fs29.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
8326
9302
|
}
|
|
8327
9303
|
var VectorIndex = class {
|
|
8328
9304
|
constructor(embedder, cachePath) {
|
|
@@ -8479,8 +9455,8 @@ var ALL_PHASES = [
|
|
|
8479
9455
|
];
|
|
8480
9456
|
function classifyChange(relPath) {
|
|
8481
9457
|
const phases = /* @__PURE__ */ new Set();
|
|
8482
|
-
const base =
|
|
8483
|
-
const segments = relPath.split(
|
|
9458
|
+
const base = import_node_path49.default.basename(relPath).toLowerCase();
|
|
9459
|
+
const segments = relPath.split(import_node_path49.default.sep).map((s) => s.toLowerCase());
|
|
8484
9460
|
if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
|
|
8485
9461
|
phases.add("services");
|
|
8486
9462
|
phases.add("aliases");
|
|
@@ -8601,16 +9577,16 @@ function countWatchableDirs(scanPath, limit) {
|
|
|
8601
9577
|
if (count >= limit) return;
|
|
8602
9578
|
let entries;
|
|
8603
9579
|
try {
|
|
8604
|
-
entries =
|
|
9580
|
+
entries = import_node_fs30.default.readdirSync(dir, { withFileTypes: true });
|
|
8605
9581
|
} catch {
|
|
8606
9582
|
return;
|
|
8607
9583
|
}
|
|
8608
9584
|
for (const e of entries) {
|
|
8609
9585
|
if (count >= limit) return;
|
|
8610
9586
|
if (!e.isDirectory()) continue;
|
|
8611
|
-
if (IGNORED_WATCH_PATHS.some((re) => re.test(
|
|
9587
|
+
if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path49.default.join(dir, e.name) + import_node_path49.default.sep))) continue;
|
|
8612
9588
|
count++;
|
|
8613
|
-
if (depth < 2) visit(
|
|
9589
|
+
if (depth < 2) visit(import_node_path49.default.join(dir, e.name), depth + 1);
|
|
8614
9590
|
}
|
|
8615
9591
|
};
|
|
8616
9592
|
visit(scanPath, 0);
|
|
@@ -8628,8 +9604,8 @@ async function startWatch(graph, opts) {
|
|
|
8628
9604
|
const projectName = opts.project ?? DEFAULT_PROJECT;
|
|
8629
9605
|
await loadGraphFromDisk(graph, opts.outPath);
|
|
8630
9606
|
const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
|
|
8631
|
-
const policyFilePath =
|
|
8632
|
-
const policyViolationsPath =
|
|
9607
|
+
const policyFilePath = import_node_path49.default.join(opts.scanPath, "policy.json");
|
|
9608
|
+
const policyViolationsPath = import_node_path49.default.join(import_node_path49.default.dirname(opts.outPath), "policy-violations.ndjson");
|
|
8633
9609
|
let policies = [];
|
|
8634
9610
|
try {
|
|
8635
9611
|
policies = await loadPolicyFile(policyFilePath);
|
|
@@ -8680,7 +9656,7 @@ async function startWatch(graph, opts) {
|
|
|
8680
9656
|
assertBindAuthority(host, auth.authToken);
|
|
8681
9657
|
const port = opts.port ?? 8080;
|
|
8682
9658
|
const otelPort = opts.otelPort ?? 4318;
|
|
8683
|
-
const cachePath = opts.embeddingsCachePath ??
|
|
9659
|
+
const cachePath = opts.embeddingsCachePath ?? import_node_path49.default.join(import_node_path49.default.dirname(opts.outPath), "embeddings.json");
|
|
8684
9660
|
let searchIndex;
|
|
8685
9661
|
try {
|
|
8686
9662
|
searchIndex = await buildSearchIndex(graph, { cachePath });
|
|
@@ -8698,7 +9674,7 @@ async function startWatch(graph, opts) {
|
|
|
8698
9674
|
// Paths are derived from the explicit options the watch caller passes
|
|
8699
9675
|
// — pathsForProject is only used to fill in the embeddings/snapshot
|
|
8700
9676
|
// fields so the registry shape is complete.
|
|
8701
|
-
...pathsForProject(projectName,
|
|
9677
|
+
...pathsForProject(projectName, import_node_path49.default.dirname(opts.outPath)),
|
|
8702
9678
|
snapshotPath: opts.outPath,
|
|
8703
9679
|
errorsPath: opts.errorsPath,
|
|
8704
9680
|
staleEventsPath: opts.staleEventsPath
|
|
@@ -8816,9 +9792,9 @@ async function startWatch(graph, opts) {
|
|
|
8816
9792
|
};
|
|
8817
9793
|
const onPath = (absPath) => {
|
|
8818
9794
|
if (shouldIgnore(absPath)) return;
|
|
8819
|
-
const rel =
|
|
9795
|
+
const rel = import_node_path49.default.relative(opts.scanPath, absPath);
|
|
8820
9796
|
if (!rel || rel.startsWith("..")) return;
|
|
8821
|
-
pendingPaths.add(rel.split(
|
|
9797
|
+
pendingPaths.add(rel.split(import_node_path49.default.sep).join("/"));
|
|
8822
9798
|
const phases = classifyChange(rel);
|
|
8823
9799
|
if (phases.size === 0) {
|
|
8824
9800
|
for (const p of ALL_PHASES) pending.add(p);
|
|
@@ -8874,8 +9850,8 @@ async function startWatch(graph, opts) {
|
|
|
8874
9850
|
|
|
8875
9851
|
// src/deploy/detect.ts
|
|
8876
9852
|
init_cjs_shims();
|
|
8877
|
-
var
|
|
8878
|
-
var
|
|
9853
|
+
var import_node_fs31 = require("fs");
|
|
9854
|
+
var import_node_path50 = __toESM(require("path"), 1);
|
|
8879
9855
|
var import_node_child_process2 = require("child_process");
|
|
8880
9856
|
var import_node_crypto4 = require("crypto");
|
|
8881
9857
|
function generateToken() {
|
|
@@ -8975,21 +9951,21 @@ async function runDeploy(opts = {}) {
|
|
|
8975
9951
|
const token = generateToken();
|
|
8976
9952
|
switch (substrate) {
|
|
8977
9953
|
case "docker-compose": {
|
|
8978
|
-
const artifactPath =
|
|
9954
|
+
const artifactPath = import_node_path50.default.join(cwd, "docker-compose.neat.yml");
|
|
8979
9955
|
const contents = emitDockerCompose(cwd);
|
|
8980
|
-
await
|
|
9956
|
+
await import_node_fs31.promises.writeFile(artifactPath, contents, "utf8");
|
|
8981
9957
|
return {
|
|
8982
9958
|
substrate,
|
|
8983
9959
|
artifactPath,
|
|
8984
9960
|
token,
|
|
8985
9961
|
contents,
|
|
8986
|
-
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${
|
|
9962
|
+
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path50.default.basename(artifactPath)} up -d`
|
|
8987
9963
|
};
|
|
8988
9964
|
}
|
|
8989
9965
|
case "systemd": {
|
|
8990
|
-
const artifactPath =
|
|
9966
|
+
const artifactPath = import_node_path50.default.join(cwd, "neat.service");
|
|
8991
9967
|
const contents = emitSystemdUnit(cwd);
|
|
8992
|
-
await
|
|
9968
|
+
await import_node_fs31.promises.writeFile(artifactPath, contents, "utf8");
|
|
8993
9969
|
return {
|
|
8994
9970
|
substrate,
|
|
8995
9971
|
artifactPath,
|
|
@@ -9022,8 +9998,8 @@ init_cjs_shims();
|
|
|
9022
9998
|
|
|
9023
9999
|
// src/installers/javascript.ts
|
|
9024
10000
|
init_cjs_shims();
|
|
9025
|
-
var
|
|
9026
|
-
var
|
|
10001
|
+
var import_node_fs32 = require("fs");
|
|
10002
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
9027
10003
|
var import_semver2 = __toESM(require("semver"), 1);
|
|
9028
10004
|
|
|
9029
10005
|
// src/installers/templates.ts
|
|
@@ -9448,6 +10424,9 @@ export async function register() {
|
|
|
9448
10424
|
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
9449
10425
|
await import('./instrumentation.node')
|
|
9450
10426
|
}
|
|
10427
|
+
if (process.env.NEXT_RUNTIME === 'edge') {
|
|
10428
|
+
await import('./instrumentation.edge')
|
|
10429
|
+
}
|
|
9451
10430
|
}
|
|
9452
10431
|
`;
|
|
9453
10432
|
var NEXT_INSTRUMENTATION_JS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
@@ -9455,6 +10434,9 @@ export async function register() {
|
|
|
9455
10434
|
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
9456
10435
|
await import('./instrumentation.node')
|
|
9457
10436
|
}
|
|
10437
|
+
if (process.env.NEXT_RUNTIME === 'edge') {
|
|
10438
|
+
await import('./instrumentation.edge')
|
|
10439
|
+
}
|
|
9458
10440
|
}
|
|
9459
10441
|
`;
|
|
9460
10442
|
var NEXT_INSTRUMENTATION_NODE_TS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
@@ -9490,6 +10472,18 @@ ${registrations.join("\n")}
|
|
|
9490
10472
|
`;
|
|
9491
10473
|
return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
|
|
9492
10474
|
}
|
|
10475
|
+
var NEXT_INSTRUMENTATION_EDGE_HEADER = "// Generated by `neat init --apply` (ADR-126). Next.js edge-runtime instrumentation via @vercel/otel.";
|
|
10476
|
+
var NEXT_INSTRUMENTATION_EDGE_TS = `${NEXT_INSTRUMENTATION_EDGE_HEADER}
|
|
10477
|
+
import { registerOTel } from '@vercel/otel'
|
|
10478
|
+
|
|
10479
|
+
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
10480
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
|
|
10481
|
+
${OTEL_OTLP_PROTOCOL_JS}
|
|
10482
|
+
${OTEL_OTLP_HEADERS_JS}
|
|
10483
|
+
|
|
10484
|
+
registerOTel({ serviceName: process.env.OTEL_SERVICE_NAME })
|
|
10485
|
+
`;
|
|
10486
|
+
var NEXT_INSTRUMENTATION_EDGE_JS = NEXT_INSTRUMENTATION_EDGE_TS;
|
|
9493
10487
|
var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074). OpenTelemetry SDK hook.";
|
|
9494
10488
|
var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
9495
10489
|
${OTEL_ESM_NODE_IMPORTS}
|
|
@@ -9590,6 +10584,7 @@ var SDK_PACKAGES = [
|
|
|
9590
10584
|
{ name: "@opentelemetry/sdk-node", version: "^0.57.0" },
|
|
9591
10585
|
{ name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" }
|
|
9592
10586
|
];
|
|
10587
|
+
var NEXT_EDGE_PACKAGES = [{ name: "@vercel/otel", version: "^2.1.3" }];
|
|
9593
10588
|
function getMajor(versionRange) {
|
|
9594
10589
|
if (!versionRange) return 0;
|
|
9595
10590
|
const match = versionRange.match(/(\d+)/);
|
|
@@ -9619,15 +10614,15 @@ var OTEL_ENV = {
|
|
|
9619
10614
|
value: "http://localhost:4318/projects/<project>/v1/traces"
|
|
9620
10615
|
};
|
|
9621
10616
|
function serviceNodeName(pkg, serviceDir) {
|
|
9622
|
-
return pkg.name ??
|
|
10617
|
+
return pkg.name ?? import_node_path51.default.basename(serviceDir);
|
|
9623
10618
|
}
|
|
9624
10619
|
function projectToken(pkg, serviceDir, project) {
|
|
9625
10620
|
if (project && project.length > 0) return project;
|
|
9626
|
-
return pkg.name ??
|
|
10621
|
+
return pkg.name ?? import_node_path51.default.basename(serviceDir);
|
|
9627
10622
|
}
|
|
9628
10623
|
async function readJsonFile(p) {
|
|
9629
10624
|
try {
|
|
9630
|
-
const raw = await
|
|
10625
|
+
const raw = await import_node_fs32.promises.readFile(p, "utf8");
|
|
9631
10626
|
return JSON.parse(raw);
|
|
9632
10627
|
} catch {
|
|
9633
10628
|
return null;
|
|
@@ -9636,16 +10631,16 @@ async function readJsonFile(p) {
|
|
|
9636
10631
|
async function detectRuntimeKind(pkgRoot, pkg) {
|
|
9637
10632
|
const deps = allDeps(pkg);
|
|
9638
10633
|
if ("react-native" in deps || "expo" in deps) return "react-native";
|
|
9639
|
-
const appJson = await readJsonFile(
|
|
10634
|
+
const appJson = await readJsonFile(import_node_path51.default.join(pkgRoot, "app.json"));
|
|
9640
10635
|
if (appJson && typeof appJson === "object" && "expo" in appJson) {
|
|
9641
10636
|
return "react-native";
|
|
9642
10637
|
}
|
|
9643
|
-
if (await exists3(
|
|
10638
|
+
if (await exists3(import_node_path51.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path51.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path51.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
|
|
9644
10639
|
return "browser-bundle";
|
|
9645
10640
|
}
|
|
9646
|
-
if (await exists3(
|
|
9647
|
-
if (await exists3(
|
|
9648
|
-
if (await exists3(
|
|
10641
|
+
if (await exists3(import_node_path51.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
|
|
10642
|
+
if (await exists3(import_node_path51.default.join(pkgRoot, "bun.lockb"))) return "bun";
|
|
10643
|
+
if (await exists3(import_node_path51.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path51.default.join(pkgRoot, "deno.lock"))) {
|
|
9649
10644
|
return "deno";
|
|
9650
10645
|
}
|
|
9651
10646
|
const engines = pkg.engines ?? {};
|
|
@@ -9654,7 +10649,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
|
|
|
9654
10649
|
}
|
|
9655
10650
|
async function readPackageJson2(serviceDir) {
|
|
9656
10651
|
try {
|
|
9657
|
-
const raw = await
|
|
10652
|
+
const raw = await import_node_fs32.promises.readFile(import_node_path51.default.join(serviceDir, "package.json"), "utf8");
|
|
9658
10653
|
return JSON.parse(raw);
|
|
9659
10654
|
} catch {
|
|
9660
10655
|
return null;
|
|
@@ -9662,7 +10657,7 @@ async function readPackageJson2(serviceDir) {
|
|
|
9662
10657
|
}
|
|
9663
10658
|
async function exists3(p) {
|
|
9664
10659
|
try {
|
|
9665
|
-
await
|
|
10660
|
+
await import_node_fs32.promises.stat(p);
|
|
9666
10661
|
return true;
|
|
9667
10662
|
} catch {
|
|
9668
10663
|
return false;
|
|
@@ -9670,7 +10665,7 @@ async function exists3(p) {
|
|
|
9670
10665
|
}
|
|
9671
10666
|
async function readFileMaybe(p) {
|
|
9672
10667
|
try {
|
|
9673
|
-
return await
|
|
10668
|
+
return await import_node_fs32.promises.readFile(p, "utf8");
|
|
9674
10669
|
} catch {
|
|
9675
10670
|
return null;
|
|
9676
10671
|
}
|
|
@@ -9698,7 +10693,7 @@ function needsVersionUpgrade(installed, expected) {
|
|
|
9698
10693
|
var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
|
|
9699
10694
|
async function findNextConfig(serviceDir) {
|
|
9700
10695
|
for (const name of NEXT_CONFIG_CANDIDATES) {
|
|
9701
|
-
const candidate =
|
|
10696
|
+
const candidate = import_node_path51.default.join(serviceDir, name);
|
|
9702
10697
|
if (await exists3(candidate)) return candidate;
|
|
9703
10698
|
}
|
|
9704
10699
|
return null;
|
|
@@ -9767,7 +10762,7 @@ function hasRemixDependency(pkg) {
|
|
|
9767
10762
|
}
|
|
9768
10763
|
async function findRemixEntry(serviceDir) {
|
|
9769
10764
|
for (const rel of REMIX_ENTRY_CANDIDATES) {
|
|
9770
|
-
const candidate =
|
|
10765
|
+
const candidate = import_node_path51.default.join(serviceDir, rel);
|
|
9771
10766
|
if (await exists3(candidate)) return candidate;
|
|
9772
10767
|
}
|
|
9773
10768
|
return null;
|
|
@@ -9779,14 +10774,14 @@ function hasSvelteKitDependency(pkg) {
|
|
|
9779
10774
|
}
|
|
9780
10775
|
async function findSvelteKitHooks(serviceDir) {
|
|
9781
10776
|
for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
|
|
9782
|
-
const candidate =
|
|
10777
|
+
const candidate = import_node_path51.default.join(serviceDir, rel);
|
|
9783
10778
|
if (await exists3(candidate)) return candidate;
|
|
9784
10779
|
}
|
|
9785
10780
|
return null;
|
|
9786
10781
|
}
|
|
9787
10782
|
async function findSvelteKitConfig(serviceDir) {
|
|
9788
10783
|
for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
|
|
9789
|
-
const candidate =
|
|
10784
|
+
const candidate = import_node_path51.default.join(serviceDir, rel);
|
|
9790
10785
|
if (await exists3(candidate)) return candidate;
|
|
9791
10786
|
}
|
|
9792
10787
|
return null;
|
|
@@ -9797,7 +10792,7 @@ function hasNuxtDependency(pkg) {
|
|
|
9797
10792
|
}
|
|
9798
10793
|
async function findNuxtConfig(serviceDir) {
|
|
9799
10794
|
for (const name of NUXT_CONFIG_CANDIDATES) {
|
|
9800
|
-
const candidate =
|
|
10795
|
+
const candidate = import_node_path51.default.join(serviceDir, name);
|
|
9801
10796
|
if (await exists3(candidate)) return candidate;
|
|
9802
10797
|
}
|
|
9803
10798
|
return null;
|
|
@@ -9808,7 +10803,7 @@ function hasAstroDependency(pkg) {
|
|
|
9808
10803
|
}
|
|
9809
10804
|
async function findAstroConfig(serviceDir) {
|
|
9810
10805
|
for (const name of ASTRO_CONFIG_CANDIDATES) {
|
|
9811
|
-
const candidate =
|
|
10806
|
+
const candidate = import_node_path51.default.join(serviceDir, name);
|
|
9812
10807
|
if (await exists3(candidate)) return candidate;
|
|
9813
10808
|
}
|
|
9814
10809
|
return null;
|
|
@@ -9822,7 +10817,7 @@ function parseNextMajor(range) {
|
|
|
9822
10817
|
return Number.isFinite(n) ? n : null;
|
|
9823
10818
|
}
|
|
9824
10819
|
async function isTypeScriptProject(serviceDir) {
|
|
9825
|
-
return exists3(
|
|
10820
|
+
return exists3(import_node_path51.default.join(serviceDir, "tsconfig.json"));
|
|
9826
10821
|
}
|
|
9827
10822
|
var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
|
|
9828
10823
|
var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
|
|
@@ -9871,7 +10866,7 @@ function entryFromScript(script) {
|
|
|
9871
10866
|
}
|
|
9872
10867
|
async function resolveEntry(serviceDir, pkg) {
|
|
9873
10868
|
if (typeof pkg.main === "string" && pkg.main.length > 0) {
|
|
9874
|
-
const candidate =
|
|
10869
|
+
const candidate = import_node_path51.default.resolve(serviceDir, pkg.main);
|
|
9875
10870
|
if (await exists3(candidate)) return candidate;
|
|
9876
10871
|
}
|
|
9877
10872
|
if (pkg.bin) {
|
|
@@ -9885,40 +10880,40 @@ async function resolveEntry(serviceDir, pkg) {
|
|
|
9885
10880
|
if (typeof first === "string") binEntry = first;
|
|
9886
10881
|
}
|
|
9887
10882
|
if (binEntry) {
|
|
9888
|
-
const candidate =
|
|
10883
|
+
const candidate = import_node_path51.default.resolve(serviceDir, binEntry);
|
|
9889
10884
|
if (await exists3(candidate)) return candidate;
|
|
9890
10885
|
}
|
|
9891
10886
|
}
|
|
9892
10887
|
const startEntry = entryFromScript(pkg.scripts?.start);
|
|
9893
10888
|
if (startEntry) {
|
|
9894
|
-
const candidate =
|
|
10889
|
+
const candidate = import_node_path51.default.resolve(serviceDir, startEntry);
|
|
9895
10890
|
if (await exists3(candidate)) return candidate;
|
|
9896
10891
|
}
|
|
9897
10892
|
const devEntry = entryFromScript(pkg.scripts?.dev);
|
|
9898
10893
|
if (devEntry) {
|
|
9899
|
-
const candidate =
|
|
10894
|
+
const candidate = import_node_path51.default.resolve(serviceDir, devEntry);
|
|
9900
10895
|
if (await exists3(candidate)) return candidate;
|
|
9901
10896
|
}
|
|
9902
10897
|
for (const rel of SRC_INDEX_CANDIDATES) {
|
|
9903
|
-
const candidate =
|
|
10898
|
+
const candidate = import_node_path51.default.join(serviceDir, rel);
|
|
9904
10899
|
if (await exists3(candidate)) return candidate;
|
|
9905
10900
|
}
|
|
9906
10901
|
for (const rel of SRC_NAMED_CANDIDATES) {
|
|
9907
|
-
const candidate =
|
|
10902
|
+
const candidate = import_node_path51.default.join(serviceDir, rel);
|
|
9908
10903
|
if (await exists3(candidate)) return candidate;
|
|
9909
10904
|
}
|
|
9910
10905
|
for (const rel of ROOT_NAMED_CANDIDATES) {
|
|
9911
|
-
const candidate =
|
|
10906
|
+
const candidate = import_node_path51.default.join(serviceDir, rel);
|
|
9912
10907
|
if (await exists3(candidate)) return candidate;
|
|
9913
10908
|
}
|
|
9914
10909
|
for (const name of INDEX_CANDIDATES) {
|
|
9915
|
-
const candidate =
|
|
10910
|
+
const candidate = import_node_path51.default.join(serviceDir, name);
|
|
9916
10911
|
if (await exists3(candidate)) return candidate;
|
|
9917
10912
|
}
|
|
9918
10913
|
return null;
|
|
9919
10914
|
}
|
|
9920
10915
|
function dispatchEntry(entryFile, pkg) {
|
|
9921
|
-
const ext =
|
|
10916
|
+
const ext = import_node_path51.default.extname(entryFile).toLowerCase();
|
|
9922
10917
|
if (ext === ".ts" || ext === ".tsx") return "ts";
|
|
9923
10918
|
if (ext === ".mjs") return "esm";
|
|
9924
10919
|
if (ext === ".cjs") return "cjs";
|
|
@@ -9935,9 +10930,9 @@ function otelInitContents(flavor) {
|
|
|
9935
10930
|
return OTEL_INIT_CJS;
|
|
9936
10931
|
}
|
|
9937
10932
|
function injectionLine(flavor, entryFile, otelInitFile) {
|
|
9938
|
-
let rel =
|
|
10933
|
+
let rel = import_node_path51.default.relative(import_node_path51.default.dirname(entryFile), otelInitFile);
|
|
9939
10934
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
9940
|
-
rel = rel.split(
|
|
10935
|
+
rel = rel.split(import_node_path51.default.sep).join("/");
|
|
9941
10936
|
if (flavor === "cjs") return `require('${rel}')`;
|
|
9942
10937
|
if (flavor === "esm") return `import '${rel}'`;
|
|
9943
10938
|
const tsRel = rel.replace(/\.ts$/, "");
|
|
@@ -9950,23 +10945,27 @@ function lineIsOtelInjection(line) {
|
|
|
9950
10945
|
}
|
|
9951
10946
|
async function detectsSrcLayout(serviceDir) {
|
|
9952
10947
|
const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
|
|
9953
|
-
exists3(
|
|
9954
|
-
exists3(
|
|
9955
|
-
exists3(
|
|
9956
|
-
exists3(
|
|
10948
|
+
exists3(import_node_path51.default.join(serviceDir, "src", "app")),
|
|
10949
|
+
exists3(import_node_path51.default.join(serviceDir, "src", "pages")),
|
|
10950
|
+
exists3(import_node_path51.default.join(serviceDir, "app")),
|
|
10951
|
+
exists3(import_node_path51.default.join(serviceDir, "pages"))
|
|
9957
10952
|
]);
|
|
9958
10953
|
return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
|
|
9959
10954
|
}
|
|
9960
10955
|
async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
|
|
9961
10956
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
9962
10957
|
const srcLayout = await detectsSrcLayout(serviceDir);
|
|
9963
|
-
const baseDir = srcLayout ?
|
|
9964
|
-
const instrumentationFile =
|
|
9965
|
-
const instrumentationNodeFile =
|
|
10958
|
+
const baseDir = srcLayout ? import_node_path51.default.join(serviceDir, "src") : serviceDir;
|
|
10959
|
+
const instrumentationFile = import_node_path51.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
|
|
10960
|
+
const instrumentationNodeFile = import_node_path51.default.join(
|
|
9966
10961
|
baseDir,
|
|
9967
10962
|
useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
|
|
9968
10963
|
);
|
|
9969
|
-
const
|
|
10964
|
+
const instrumentationEdgeFile = import_node_path51.default.join(
|
|
10965
|
+
baseDir,
|
|
10966
|
+
useTs ? "instrumentation.edge.ts" : "instrumentation.edge.js"
|
|
10967
|
+
);
|
|
10968
|
+
const envNeatFile = import_node_path51.default.join(baseDir, ".env.neat");
|
|
9970
10969
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
9971
10970
|
const dependencyEdits = [];
|
|
9972
10971
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -9978,6 +10977,15 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
9978
10977
|
}
|
|
9979
10978
|
dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
|
|
9980
10979
|
}
|
|
10980
|
+
for (const sdk of NEXT_EDGE_PACKAGES) {
|
|
10981
|
+
if (sdk.name in existingDeps) {
|
|
10982
|
+
if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
|
|
10983
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
|
|
10984
|
+
}
|
|
10985
|
+
continue;
|
|
10986
|
+
}
|
|
10987
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
|
|
10988
|
+
}
|
|
9981
10989
|
const nonBundled = detectNonBundledInstrumentations(pkg);
|
|
9982
10990
|
for (const inst of nonBundled) {
|
|
9983
10991
|
if (inst.pkg in existingDeps) {
|
|
@@ -10011,6 +11019,17 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
10011
11019
|
skipIfExists: true
|
|
10012
11020
|
});
|
|
10013
11021
|
}
|
|
11022
|
+
if (!await exists3(instrumentationEdgeFile)) {
|
|
11023
|
+
generatedFiles.push({
|
|
11024
|
+
file: instrumentationEdgeFile,
|
|
11025
|
+
contents: renderFrameworkOtelInit(
|
|
11026
|
+
useTs ? NEXT_INSTRUMENTATION_EDGE_TS : NEXT_INSTRUMENTATION_EDGE_JS,
|
|
11027
|
+
svcName,
|
|
11028
|
+
projectName
|
|
11029
|
+
),
|
|
11030
|
+
skipIfExists: true
|
|
11031
|
+
});
|
|
11032
|
+
}
|
|
10014
11033
|
if (!await exists3(envNeatFile)) {
|
|
10015
11034
|
generatedFiles.push({
|
|
10016
11035
|
file: envNeatFile,
|
|
@@ -10023,7 +11042,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
10023
11042
|
const nextMajor = parseNextMajor(nextRange);
|
|
10024
11043
|
if (nextMajor !== null && nextMajor < 15) {
|
|
10025
11044
|
try {
|
|
10026
|
-
const raw = await
|
|
11045
|
+
const raw = await import_node_fs32.promises.readFile(nextConfigPath, "utf8");
|
|
10027
11046
|
if (!raw.includes("instrumentationHook")) {
|
|
10028
11047
|
nextConfigEdit = {
|
|
10029
11048
|
file: nextConfigPath,
|
|
@@ -10071,7 +11090,7 @@ function buildDependencyEdits(pkg, manifestPath) {
|
|
|
10071
11090
|
return edits;
|
|
10072
11091
|
}
|
|
10073
11092
|
async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
|
|
10074
|
-
const envNeatFile =
|
|
11093
|
+
const envNeatFile = import_node_path51.default.join(serviceDir, ".env.neat");
|
|
10075
11094
|
if (!await exists3(envNeatFile)) {
|
|
10076
11095
|
generatedFiles.push({
|
|
10077
11096
|
file: envNeatFile,
|
|
@@ -10106,7 +11125,7 @@ function fileImportsOtelHook(raw, specifiers) {
|
|
|
10106
11125
|
}
|
|
10107
11126
|
async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
10108
11127
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
10109
|
-
const otelServerFile =
|
|
11128
|
+
const otelServerFile = import_node_path51.default.join(
|
|
10110
11129
|
serviceDir,
|
|
10111
11130
|
useTs ? "app/otel.server.ts" : "app/otel.server.js"
|
|
10112
11131
|
);
|
|
@@ -10127,7 +11146,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
10127
11146
|
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
10128
11147
|
const entrypointEdits = [];
|
|
10129
11148
|
try {
|
|
10130
|
-
const raw = await
|
|
11149
|
+
const raw = await import_node_fs32.promises.readFile(entryFile, "utf8");
|
|
10131
11150
|
if (!fileImportsOtelHook(raw, ["./otel.server"])) {
|
|
10132
11151
|
const lines = raw.split(/\r?\n/);
|
|
10133
11152
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -10163,11 +11182,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
10163
11182
|
}
|
|
10164
11183
|
async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
|
|
10165
11184
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
10166
|
-
const otelInitFile =
|
|
11185
|
+
const otelInitFile = import_node_path51.default.join(
|
|
10167
11186
|
serviceDir,
|
|
10168
11187
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
10169
11188
|
);
|
|
10170
|
-
const resolvedHooksFile = hooksFile ??
|
|
11189
|
+
const resolvedHooksFile = hooksFile ?? import_node_path51.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
|
|
10171
11190
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
10172
11191
|
const generatedFiles = [];
|
|
10173
11192
|
const entrypointEdits = [];
|
|
@@ -10192,7 +11211,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
10192
11211
|
});
|
|
10193
11212
|
} else {
|
|
10194
11213
|
try {
|
|
10195
|
-
const raw = await
|
|
11214
|
+
const raw = await import_node_fs32.promises.readFile(hooksFile, "utf8");
|
|
10196
11215
|
if (!fileImportsOtelHook(raw, ["./otel-init"])) {
|
|
10197
11216
|
const lines = raw.split(/\r?\n/);
|
|
10198
11217
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -10229,11 +11248,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
10229
11248
|
}
|
|
10230
11249
|
async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
10231
11250
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
10232
|
-
const otelPluginFile =
|
|
11251
|
+
const otelPluginFile = import_node_path51.default.join(
|
|
10233
11252
|
serviceDir,
|
|
10234
11253
|
useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
|
|
10235
11254
|
);
|
|
10236
|
-
const otelInitFile =
|
|
11255
|
+
const otelInitFile = import_node_path51.default.join(
|
|
10237
11256
|
serviceDir,
|
|
10238
11257
|
useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
|
|
10239
11258
|
);
|
|
@@ -10284,19 +11303,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
|
10284
11303
|
var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
|
|
10285
11304
|
async function findAstroMiddleware(serviceDir) {
|
|
10286
11305
|
for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
|
|
10287
|
-
const candidate =
|
|
11306
|
+
const candidate = import_node_path51.default.join(serviceDir, rel);
|
|
10288
11307
|
if (await exists3(candidate)) return candidate;
|
|
10289
11308
|
}
|
|
10290
11309
|
return null;
|
|
10291
11310
|
}
|
|
10292
11311
|
async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
10293
11312
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
10294
|
-
const otelInitFile =
|
|
11313
|
+
const otelInitFile = import_node_path51.default.join(
|
|
10295
11314
|
serviceDir,
|
|
10296
11315
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
10297
11316
|
);
|
|
10298
11317
|
const existingMiddleware = await findAstroMiddleware(serviceDir);
|
|
10299
|
-
const middlewareFile = existingMiddleware ??
|
|
11318
|
+
const middlewareFile = existingMiddleware ?? import_node_path51.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
|
|
10300
11319
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
10301
11320
|
const generatedFiles = [];
|
|
10302
11321
|
const entrypointEdits = [];
|
|
@@ -10321,7 +11340,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
|
10321
11340
|
});
|
|
10322
11341
|
} else {
|
|
10323
11342
|
try {
|
|
10324
|
-
const raw = await
|
|
11343
|
+
const raw = await import_node_fs32.promises.readFile(existingMiddleware, "utf8");
|
|
10325
11344
|
if (!fileImportsOtelHook(raw, ["./otel-init"])) {
|
|
10326
11345
|
const lines = raw.split(/\r?\n/);
|
|
10327
11346
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -10392,7 +11411,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
|
|
|
10392
11411
|
}
|
|
10393
11412
|
async function plan(serviceDir, opts) {
|
|
10394
11413
|
const pkg = await readPackageJson2(serviceDir);
|
|
10395
|
-
const manifestPath =
|
|
11414
|
+
const manifestPath = import_node_path51.default.join(serviceDir, "package.json");
|
|
10396
11415
|
const project = opts?.project;
|
|
10397
11416
|
const empty = {
|
|
10398
11417
|
language: "javascript",
|
|
@@ -10427,8 +11446,8 @@ async function plan(serviceDir, opts) {
|
|
|
10427
11446
|
return { ...empty, libOnly: true };
|
|
10428
11447
|
}
|
|
10429
11448
|
const flavor = dispatchEntry(entryFile, pkg);
|
|
10430
|
-
const otelInitFile =
|
|
10431
|
-
const envNeatFile =
|
|
11449
|
+
const otelInitFile = import_node_path51.default.join(import_node_path51.default.dirname(entryFile), otelInitFilename(flavor));
|
|
11450
|
+
const envNeatFile = import_node_path51.default.join(serviceDir, ".env.neat");
|
|
10432
11451
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
10433
11452
|
const dependencyEdits = [];
|
|
10434
11453
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -10452,7 +11471,7 @@ async function plan(serviceDir, opts) {
|
|
|
10452
11471
|
}
|
|
10453
11472
|
const entrypointEdits = [];
|
|
10454
11473
|
try {
|
|
10455
|
-
const raw = await
|
|
11474
|
+
const raw = await import_node_fs32.promises.readFile(entryFile, "utf8");
|
|
10456
11475
|
const lines = raw.split(/\r?\n/);
|
|
10457
11476
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
10458
11477
|
if (!lineIsOtelInjection(firstReal)) {
|
|
@@ -10497,14 +11516,14 @@ async function plan(serviceDir, opts) {
|
|
|
10497
11516
|
};
|
|
10498
11517
|
}
|
|
10499
11518
|
function isAllowedWritePath(serviceDir, target) {
|
|
10500
|
-
const rel =
|
|
11519
|
+
const rel = import_node_path51.default.relative(serviceDir, target);
|
|
10501
11520
|
if (rel.startsWith("..")) return false;
|
|
10502
|
-
const base =
|
|
11521
|
+
const base = import_node_path51.default.basename(target);
|
|
10503
11522
|
if (base === "package.json") return true;
|
|
10504
11523
|
if (base === ".env.neat") return true;
|
|
10505
11524
|
if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
|
|
10506
|
-
const relPosix = rel.split(
|
|
10507
|
-
if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
|
|
11525
|
+
const relPosix = rel.split(import_node_path51.default.sep).join("/");
|
|
11526
|
+
if (/^instrumentation(?:\.(?:node|edge))?\.(?:js|cjs|mjs|ts)$/.test(base)) {
|
|
10508
11527
|
if (relPosix === base) return true;
|
|
10509
11528
|
if (relPosix === `src/${base}`) return true;
|
|
10510
11529
|
return false;
|
|
@@ -10520,10 +11539,10 @@ function isAllowedWritePath(serviceDir, target) {
|
|
|
10520
11539
|
return false;
|
|
10521
11540
|
}
|
|
10522
11541
|
async function writeAtomic(file, contents) {
|
|
10523
|
-
await
|
|
11542
|
+
await import_node_fs32.promises.mkdir(import_node_path51.default.dirname(file), { recursive: true });
|
|
10524
11543
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
10525
|
-
await
|
|
10526
|
-
await
|
|
11544
|
+
await import_node_fs32.promises.writeFile(tmp, contents, "utf8");
|
|
11545
|
+
await import_node_fs32.promises.rename(tmp, file);
|
|
10527
11546
|
}
|
|
10528
11547
|
async function apply(installPlan) {
|
|
10529
11548
|
const { serviceDir } = installPlan;
|
|
@@ -10599,7 +11618,7 @@ async function apply(installPlan) {
|
|
|
10599
11618
|
for (const target of allTargets) {
|
|
10600
11619
|
if (await exists3(target)) {
|
|
10601
11620
|
try {
|
|
10602
|
-
originals.set(target, await
|
|
11621
|
+
originals.set(target, await import_node_fs32.promises.readFile(target, "utf8"));
|
|
10603
11622
|
} catch {
|
|
10604
11623
|
}
|
|
10605
11624
|
}
|
|
@@ -10682,14 +11701,14 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
10682
11701
|
const removed = [];
|
|
10683
11702
|
for (const [file, raw] of originals.entries()) {
|
|
10684
11703
|
try {
|
|
10685
|
-
await
|
|
11704
|
+
await import_node_fs32.promises.writeFile(file, raw, "utf8");
|
|
10686
11705
|
restored.push(file);
|
|
10687
11706
|
} catch {
|
|
10688
11707
|
}
|
|
10689
11708
|
}
|
|
10690
11709
|
for (const file of createdFiles) {
|
|
10691
11710
|
try {
|
|
10692
|
-
await
|
|
11711
|
+
await import_node_fs32.promises.unlink(file);
|
|
10693
11712
|
removed.push(file);
|
|
10694
11713
|
} catch {
|
|
10695
11714
|
}
|
|
@@ -10704,8 +11723,8 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
10704
11723
|
...removed.map((f) => `removed: ${f}`),
|
|
10705
11724
|
""
|
|
10706
11725
|
];
|
|
10707
|
-
const rollbackPath =
|
|
10708
|
-
await
|
|
11726
|
+
const rollbackPath = import_node_path51.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
11727
|
+
await import_node_fs32.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
10709
11728
|
}
|
|
10710
11729
|
function injectInstrumentationHook(raw) {
|
|
10711
11730
|
if (raw.includes("instrumentationHook")) return raw;
|
|
@@ -10734,8 +11753,8 @@ var javascriptInstaller = {
|
|
|
10734
11753
|
|
|
10735
11754
|
// src/installers/python.ts
|
|
10736
11755
|
init_cjs_shims();
|
|
10737
|
-
var
|
|
10738
|
-
var
|
|
11756
|
+
var import_node_fs33 = require("fs");
|
|
11757
|
+
var import_node_path52 = __toESM(require("path"), 1);
|
|
10739
11758
|
var SDK_PACKAGES2 = [
|
|
10740
11759
|
{ name: "opentelemetry-distro", version: ">=0.49b0" },
|
|
10741
11760
|
{ name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
|
|
@@ -10747,7 +11766,7 @@ var OTEL_ENV2 = {
|
|
|
10747
11766
|
};
|
|
10748
11767
|
async function exists4(p) {
|
|
10749
11768
|
try {
|
|
10750
|
-
await
|
|
11769
|
+
await import_node_fs33.promises.stat(p);
|
|
10751
11770
|
return true;
|
|
10752
11771
|
} catch {
|
|
10753
11772
|
return false;
|
|
@@ -10756,7 +11775,7 @@ async function exists4(p) {
|
|
|
10756
11775
|
async function detect2(serviceDir) {
|
|
10757
11776
|
const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
|
|
10758
11777
|
for (const m of markers) {
|
|
10759
|
-
if (await exists4(
|
|
11778
|
+
if (await exists4(import_node_path52.default.join(serviceDir, m))) return true;
|
|
10760
11779
|
}
|
|
10761
11780
|
return false;
|
|
10762
11781
|
}
|
|
@@ -10766,9 +11785,9 @@ function reqPackageName(line) {
|
|
|
10766
11785
|
return head.replace(/[<>=!~].*$/, "").toLowerCase();
|
|
10767
11786
|
}
|
|
10768
11787
|
async function planRequirementsTxtEdits(serviceDir) {
|
|
10769
|
-
const file =
|
|
11788
|
+
const file = import_node_path52.default.join(serviceDir, "requirements.txt");
|
|
10770
11789
|
if (!await exists4(file)) return null;
|
|
10771
|
-
const raw = await
|
|
11790
|
+
const raw = await import_node_fs33.promises.readFile(file, "utf8");
|
|
10772
11791
|
const presentNames = new Set(
|
|
10773
11792
|
raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
|
|
10774
11793
|
);
|
|
@@ -10776,9 +11795,9 @@ async function planRequirementsTxtEdits(serviceDir) {
|
|
|
10776
11795
|
return { manifest: file, missing: [...missing] };
|
|
10777
11796
|
}
|
|
10778
11797
|
async function planProcfileEdits(serviceDir) {
|
|
10779
|
-
const procfile =
|
|
11798
|
+
const procfile = import_node_path52.default.join(serviceDir, "Procfile");
|
|
10780
11799
|
if (!await exists4(procfile)) return [];
|
|
10781
|
-
const raw = await
|
|
11800
|
+
const raw = await import_node_fs33.promises.readFile(procfile, "utf8");
|
|
10782
11801
|
const edits = [];
|
|
10783
11802
|
for (const line of raw.split(/\r?\n/)) {
|
|
10784
11803
|
if (line.length === 0) continue;
|
|
@@ -10830,8 +11849,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
|
|
|
10830
11849
|
const next = `${original}${trailing}${newlines.join("\n")}
|
|
10831
11850
|
`;
|
|
10832
11851
|
const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
|
|
10833
|
-
await
|
|
10834
|
-
await
|
|
11852
|
+
await import_node_fs33.promises.writeFile(tmp, next, "utf8");
|
|
11853
|
+
await import_node_fs33.promises.rename(tmp, manifest);
|
|
10835
11854
|
}
|
|
10836
11855
|
async function applyProcfile(procfile, edits, original) {
|
|
10837
11856
|
let next = original;
|
|
@@ -10840,8 +11859,8 @@ async function applyProcfile(procfile, edits, original) {
|
|
|
10840
11859
|
next = next.replace(e.before, e.after);
|
|
10841
11860
|
}
|
|
10842
11861
|
const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
|
|
10843
|
-
await
|
|
10844
|
-
await
|
|
11862
|
+
await import_node_fs33.promises.writeFile(tmp, next, "utf8");
|
|
11863
|
+
await import_node_fs33.promises.rename(tmp, procfile);
|
|
10845
11864
|
}
|
|
10846
11865
|
async function apply2(installPlan) {
|
|
10847
11866
|
const { serviceDir } = installPlan;
|
|
@@ -10854,7 +11873,7 @@ async function apply2(installPlan) {
|
|
|
10854
11873
|
const originals = /* @__PURE__ */ new Map();
|
|
10855
11874
|
for (const file of touched) {
|
|
10856
11875
|
try {
|
|
10857
|
-
originals.set(file, await
|
|
11876
|
+
originals.set(file, await import_node_fs33.promises.readFile(file, "utf8"));
|
|
10858
11877
|
} catch {
|
|
10859
11878
|
}
|
|
10860
11879
|
}
|
|
@@ -10865,7 +11884,7 @@ async function apply2(installPlan) {
|
|
|
10865
11884
|
if (raw === void 0) {
|
|
10866
11885
|
throw new Error(`python installer: cannot read ${file} during apply`);
|
|
10867
11886
|
}
|
|
10868
|
-
const base =
|
|
11887
|
+
const base = import_node_path52.default.basename(file);
|
|
10869
11888
|
if (base === "requirements.txt") {
|
|
10870
11889
|
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
10871
11890
|
if (edits.length > 0) {
|
|
@@ -10890,7 +11909,7 @@ async function rollback2(installPlan, originals) {
|
|
|
10890
11909
|
const restored = [];
|
|
10891
11910
|
for (const [file, raw] of originals.entries()) {
|
|
10892
11911
|
try {
|
|
10893
|
-
await
|
|
11912
|
+
await import_node_fs33.promises.writeFile(file, raw, "utf8");
|
|
10894
11913
|
restored.push(file);
|
|
10895
11914
|
} catch {
|
|
10896
11915
|
}
|
|
@@ -10904,8 +11923,8 @@ async function rollback2(installPlan, originals) {
|
|
|
10904
11923
|
...restored.map((f) => `restored: ${f}`),
|
|
10905
11924
|
""
|
|
10906
11925
|
];
|
|
10907
|
-
const rollbackPath =
|
|
10908
|
-
await
|
|
11926
|
+
const rollbackPath = import_node_path52.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
11927
|
+
await import_node_fs33.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
10909
11928
|
}
|
|
10910
11929
|
var pythonInstaller = {
|
|
10911
11930
|
name: "python",
|
|
@@ -11028,10 +12047,10 @@ function renderPatch(sections) {
|
|
|
11028
12047
|
|
|
11029
12048
|
// src/orchestrator.ts
|
|
11030
12049
|
init_cjs_shims();
|
|
11031
|
-
var
|
|
12050
|
+
var import_node_fs34 = require("fs");
|
|
11032
12051
|
var import_node_http = __toESM(require("http"), 1);
|
|
11033
12052
|
var import_node_net = __toESM(require("net"), 1);
|
|
11034
|
-
var
|
|
12053
|
+
var import_node_path53 = __toESM(require("path"), 1);
|
|
11035
12054
|
var import_node_child_process3 = require("child_process");
|
|
11036
12055
|
var import_node_readline = __toESM(require("readline"), 1);
|
|
11037
12056
|
async function extractAndPersist(opts) {
|
|
@@ -11040,7 +12059,7 @@ async function extractAndPersist(opts) {
|
|
|
11040
12059
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
11041
12060
|
resetGraph(graphKey);
|
|
11042
12061
|
const graph = getGraph(graphKey);
|
|
11043
|
-
const projectPaths = pathsForProject(graphKey,
|
|
12062
|
+
const projectPaths = pathsForProject(graphKey, import_node_path53.default.join(opts.scanPath, "neat-out"));
|
|
11044
12063
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
11045
12064
|
errorsPath: projectPaths.errorsPath
|
|
11046
12065
|
});
|
|
@@ -11092,7 +12111,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
11092
12111
|
libOnly++;
|
|
11093
12112
|
const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
|
|
11094
12113
|
if (appDeps.length > 0) {
|
|
11095
|
-
const svcName =
|
|
12114
|
+
const svcName = import_node_path53.default.basename(svc.dir);
|
|
11096
12115
|
const list = appDeps.join(", ");
|
|
11097
12116
|
console.warn(
|
|
11098
12117
|
`neat: runtime layer won't engage for ${svcName}: no entry point found.
|
|
@@ -11105,7 +12124,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
11105
12124
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
11106
12125
|
} else if (outcome.outcome === "react-native") {
|
|
11107
12126
|
reactNative++;
|
|
11108
|
-
const svcName =
|
|
12127
|
+
const svcName = import_node_path53.default.basename(svc.dir);
|
|
11109
12128
|
console.log(
|
|
11110
12129
|
`neat: ${svc.dir} detected as React Native / Expo
|
|
11111
12130
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -11116,7 +12135,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
11116
12135
|
);
|
|
11117
12136
|
} else if (outcome.outcome === "bun") {
|
|
11118
12137
|
bun++;
|
|
11119
|
-
const svcName =
|
|
12138
|
+
const svcName = import_node_path53.default.basename(svc.dir);
|
|
11120
12139
|
console.log(
|
|
11121
12140
|
`neat: ${svc.dir} detected as Bun
|
|
11122
12141
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -11127,7 +12146,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
11127
12146
|
);
|
|
11128
12147
|
} else if (outcome.outcome === "deno") {
|
|
11129
12148
|
deno++;
|
|
11130
|
-
const svcName =
|
|
12149
|
+
const svcName = import_node_path53.default.basename(svc.dir);
|
|
11131
12150
|
console.log(
|
|
11132
12151
|
`neat: ${svc.dir} detected as Deno
|
|
11133
12152
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -11138,7 +12157,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
11138
12157
|
);
|
|
11139
12158
|
} else if (outcome.outcome === "cloudflare-workers") {
|
|
11140
12159
|
cloudflareWorkers++;
|
|
11141
|
-
const svcName =
|
|
12160
|
+
const svcName = import_node_path53.default.basename(svc.dir);
|
|
11142
12161
|
console.log(
|
|
11143
12162
|
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
11144
12163
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -11149,7 +12168,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
11149
12168
|
);
|
|
11150
12169
|
} else if (outcome.outcome === "electron") {
|
|
11151
12170
|
electron++;
|
|
11152
|
-
const svcName =
|
|
12171
|
+
const svcName = import_node_path53.default.basename(svc.dir);
|
|
11153
12172
|
console.log(
|
|
11154
12173
|
`neat: ${svc.dir} detected as Electron
|
|
11155
12174
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -11162,7 +12181,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
11162
12181
|
if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
|
|
11163
12182
|
const gaps = uninstrumentedLibraries(svc.pkg);
|
|
11164
12183
|
if (gaps.length > 0) {
|
|
11165
|
-
const svcName =
|
|
12184
|
+
const svcName = import_node_path53.default.basename(svc.dir);
|
|
11166
12185
|
const list = gaps.join(", ");
|
|
11167
12186
|
const subject = gaps.length === 1 ? "this library" : "these libraries";
|
|
11168
12187
|
const aux = gaps.length === 1 ? "isn't" : "aren't";
|
|
@@ -11368,24 +12387,24 @@ async function persistedPortsFor(scanPath) {
|
|
|
11368
12387
|
return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
|
|
11369
12388
|
}
|
|
11370
12389
|
async function acquireSpawnLock(scanPath) {
|
|
11371
|
-
const lockPath =
|
|
11372
|
-
await
|
|
12390
|
+
const lockPath = import_node_path53.default.join(scanPath, "neat-out", "daemon.spawn.lock");
|
|
12391
|
+
await import_node_fs34.promises.mkdir(import_node_path53.default.dirname(lockPath), { recursive: true });
|
|
11373
12392
|
const STALE_LOCK_MS = 6e4;
|
|
11374
12393
|
try {
|
|
11375
|
-
const fd = await
|
|
12394
|
+
const fd = await import_node_fs34.promises.open(lockPath, "wx");
|
|
11376
12395
|
await fd.writeFile(`${process.pid}
|
|
11377
12396
|
`, "utf8");
|
|
11378
12397
|
await fd.close();
|
|
11379
12398
|
return async () => {
|
|
11380
|
-
await
|
|
12399
|
+
await import_node_fs34.promises.unlink(lockPath).catch(() => {
|
|
11381
12400
|
});
|
|
11382
12401
|
};
|
|
11383
12402
|
} catch (err) {
|
|
11384
12403
|
if (err.code !== "EEXIST") return null;
|
|
11385
12404
|
try {
|
|
11386
|
-
const stat = await
|
|
12405
|
+
const stat = await import_node_fs34.promises.stat(lockPath);
|
|
11387
12406
|
if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
|
|
11388
|
-
await
|
|
12407
|
+
await import_node_fs34.promises.unlink(lockPath).catch(() => {
|
|
11389
12408
|
});
|
|
11390
12409
|
return acquireSpawnLock(scanPath);
|
|
11391
12410
|
}
|
|
@@ -11414,13 +12433,13 @@ async function healthIsForProject(restPort, project) {
|
|
|
11414
12433
|
return false;
|
|
11415
12434
|
}
|
|
11416
12435
|
function daemonLogPath(projectPath2) {
|
|
11417
|
-
return
|
|
12436
|
+
return import_node_path53.default.join(projectPath2, "neat-out", "daemon.log");
|
|
11418
12437
|
}
|
|
11419
12438
|
function spawnDaemonDetached(spec) {
|
|
11420
|
-
const here =
|
|
12439
|
+
const here = import_node_path53.default.dirname(new URL(importMetaUrl).pathname);
|
|
11421
12440
|
const candidates = [
|
|
11422
|
-
|
|
11423
|
-
|
|
12441
|
+
import_node_path53.default.join(here, "neatd.cjs"),
|
|
12442
|
+
import_node_path53.default.join(here, "neatd.js")
|
|
11424
12443
|
];
|
|
11425
12444
|
let entry2 = null;
|
|
11426
12445
|
const fsSync = require("fs");
|
|
@@ -11450,7 +12469,7 @@ function spawnDaemonDetached(spec) {
|
|
|
11450
12469
|
let logFd = null;
|
|
11451
12470
|
if (spec) {
|
|
11452
12471
|
const logPath = daemonLogPath(spec.projectPath);
|
|
11453
|
-
fsSync.mkdirSync(
|
|
12472
|
+
fsSync.mkdirSync(import_node_path53.default.dirname(logPath), { recursive: true });
|
|
11454
12473
|
logFd = fsSync.openSync(logPath, "a");
|
|
11455
12474
|
}
|
|
11456
12475
|
const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
|
|
@@ -11489,7 +12508,7 @@ async function runOrchestrator(opts) {
|
|
|
11489
12508
|
browser: "skipped"
|
|
11490
12509
|
}
|
|
11491
12510
|
};
|
|
11492
|
-
const stat = await
|
|
12511
|
+
const stat = await import_node_fs34.promises.stat(opts.scanPath).catch(() => null);
|
|
11493
12512
|
if (!stat || !stat.isDirectory()) {
|
|
11494
12513
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
11495
12514
|
result.exitCode = 2;
|
|
@@ -11649,7 +12668,7 @@ async function runOrchestrator(opts) {
|
|
|
11649
12668
|
result.steps.browser = openBrowser(dashboardUrl);
|
|
11650
12669
|
}
|
|
11651
12670
|
const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
|
|
11652
|
-
const daemonLog = daemonRunning ?
|
|
12671
|
+
const daemonLog = daemonRunning ? import_node_path53.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
|
|
11653
12672
|
printSummary(result, graph, dashboardUrl, daemonLog);
|
|
11654
12673
|
return result;
|
|
11655
12674
|
}
|
|
@@ -11687,11 +12706,11 @@ function printSummary(result, graph, dashboardUrl, daemonLog) {
|
|
|
11687
12706
|
|
|
11688
12707
|
// src/cli-verbs.ts
|
|
11689
12708
|
init_cjs_shims();
|
|
11690
|
-
var
|
|
12709
|
+
var import_node_path54 = __toESM(require("path"), 1);
|
|
11691
12710
|
|
|
11692
12711
|
// src/cli-client.ts
|
|
11693
12712
|
init_cjs_shims();
|
|
11694
|
-
var
|
|
12713
|
+
var import_types32 = require("@neat.is/types");
|
|
11695
12714
|
var HttpError = class extends Error {
|
|
11696
12715
|
constructor(status2, message, responseBody = "") {
|
|
11697
12716
|
super(message);
|
|
@@ -11716,10 +12735,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
11716
12735
|
const root = baseUrl.replace(/\/$/, "");
|
|
11717
12736
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
11718
12737
|
return {
|
|
11719
|
-
async get(
|
|
12738
|
+
async get(path56) {
|
|
11720
12739
|
let res;
|
|
11721
12740
|
try {
|
|
11722
|
-
res = await fetch(`${root}${
|
|
12741
|
+
res = await fetch(`${root}${path56}`, {
|
|
11723
12742
|
headers: { ...authHeader }
|
|
11724
12743
|
});
|
|
11725
12744
|
} catch (err) {
|
|
@@ -11731,16 +12750,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
11731
12750
|
const body = await res.text().catch(() => "");
|
|
11732
12751
|
throw new HttpError(
|
|
11733
12752
|
res.status,
|
|
11734
|
-
`${res.status} ${res.statusText} on GET ${
|
|
12753
|
+
`${res.status} ${res.statusText} on GET ${path56}: ${body}`,
|
|
11735
12754
|
body
|
|
11736
12755
|
);
|
|
11737
12756
|
}
|
|
11738
12757
|
return await res.json();
|
|
11739
12758
|
},
|
|
11740
|
-
async post(
|
|
12759
|
+
async post(path56, body) {
|
|
11741
12760
|
let res;
|
|
11742
12761
|
try {
|
|
11743
|
-
res = await fetch(`${root}${
|
|
12762
|
+
res = await fetch(`${root}${path56}`, {
|
|
11744
12763
|
method: "POST",
|
|
11745
12764
|
headers: { "content-type": "application/json", ...authHeader },
|
|
11746
12765
|
body: JSON.stringify(body)
|
|
@@ -11754,7 +12773,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
11754
12773
|
const text = await res.text().catch(() => "");
|
|
11755
12774
|
throw new HttpError(
|
|
11756
12775
|
res.status,
|
|
11757
|
-
`${res.status} ${res.statusText} on POST ${
|
|
12776
|
+
`${res.status} ${res.statusText} on POST ${path56}: ${text}`,
|
|
11758
12777
|
text
|
|
11759
12778
|
);
|
|
11760
12779
|
}
|
|
@@ -11768,12 +12787,12 @@ function projectPath(project, suffix) {
|
|
|
11768
12787
|
}
|
|
11769
12788
|
async function runRootCause(client, input) {
|
|
11770
12789
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
11771
|
-
const
|
|
12790
|
+
const path56 = projectPath(
|
|
11772
12791
|
input.project,
|
|
11773
12792
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
11774
12793
|
);
|
|
11775
12794
|
try {
|
|
11776
|
-
const result = await client.get(
|
|
12795
|
+
const result = await client.get(path56);
|
|
11777
12796
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
11778
12797
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
11779
12798
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -11799,12 +12818,12 @@ async function runRootCause(client, input) {
|
|
|
11799
12818
|
}
|
|
11800
12819
|
async function runBlastRadius(client, input) {
|
|
11801
12820
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
11802
|
-
const
|
|
12821
|
+
const path56 = projectPath(
|
|
11803
12822
|
input.project,
|
|
11804
12823
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
11805
12824
|
);
|
|
11806
12825
|
try {
|
|
11807
|
-
const result = await client.get(
|
|
12826
|
+
const result = await client.get(path56);
|
|
11808
12827
|
if (result.totalAffected === 0) {
|
|
11809
12828
|
return {
|
|
11810
12829
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -11833,17 +12852,17 @@ async function runBlastRadius(client, input) {
|
|
|
11833
12852
|
}
|
|
11834
12853
|
}
|
|
11835
12854
|
function formatBlastEntry(n) {
|
|
11836
|
-
const tag = n.edgeProvenance ===
|
|
12855
|
+
const tag = n.edgeProvenance === import_types32.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
|
|
11837
12856
|
return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
|
|
11838
12857
|
}
|
|
11839
12858
|
async function runDependencies(client, input) {
|
|
11840
12859
|
const depth = input.depth ?? 3;
|
|
11841
|
-
const
|
|
12860
|
+
const path56 = projectPath(
|
|
11842
12861
|
input.project,
|
|
11843
12862
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
11844
12863
|
);
|
|
11845
12864
|
try {
|
|
11846
|
-
const result = await client.get(
|
|
12865
|
+
const result = await client.get(path56);
|
|
11847
12866
|
if (result.total === 0) {
|
|
11848
12867
|
return {
|
|
11849
12868
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -11890,7 +12909,7 @@ async function runObservedDependencies(client, input) {
|
|
|
11890
12909
|
if (result.observed) {
|
|
11891
12910
|
return {
|
|
11892
12911
|
summary: `${input.nodeId} makes no outbound runtime calls, but OTel has observed it receiving traffic on ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 it's a pure receiver.`,
|
|
11893
|
-
provenance:
|
|
12912
|
+
provenance: import_types32.Provenance.OBSERVED
|
|
11894
12913
|
};
|
|
11895
12914
|
}
|
|
11896
12915
|
const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
|
|
@@ -11900,7 +12919,7 @@ async function runObservedDependencies(client, input) {
|
|
|
11900
12919
|
return {
|
|
11901
12920
|
summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
|
|
11902
12921
|
block: blockLines.join("\n"),
|
|
11903
|
-
provenance:
|
|
12922
|
+
provenance: import_types32.Provenance.OBSERVED
|
|
11904
12923
|
};
|
|
11905
12924
|
} catch (err) {
|
|
11906
12925
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -11935,9 +12954,9 @@ function formatDuration(ms) {
|
|
|
11935
12954
|
return `${Math.round(h / 24)}d`;
|
|
11936
12955
|
}
|
|
11937
12956
|
async function runIncidents(client, input) {
|
|
11938
|
-
const
|
|
12957
|
+
const path56 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
11939
12958
|
try {
|
|
11940
|
-
const body = await client.get(
|
|
12959
|
+
const body = await client.get(path56);
|
|
11941
12960
|
const events = body.events;
|
|
11942
12961
|
if (events.length === 0) {
|
|
11943
12962
|
return {
|
|
@@ -11954,7 +12973,7 @@ async function runIncidents(client, input) {
|
|
|
11954
12973
|
return {
|
|
11955
12974
|
summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
|
|
11956
12975
|
block: blockLines.join("\n"),
|
|
11957
|
-
provenance:
|
|
12976
|
+
provenance: import_types32.Provenance.OBSERVED
|
|
11958
12977
|
};
|
|
11959
12978
|
} catch (err) {
|
|
11960
12979
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -12063,7 +13082,7 @@ async function runStaleEdges(client, input) {
|
|
|
12063
13082
|
return {
|
|
12064
13083
|
summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
|
|
12065
13084
|
block: blockLines.join("\n"),
|
|
12066
|
-
provenance:
|
|
13085
|
+
provenance: import_types32.Provenance.STALE
|
|
12067
13086
|
};
|
|
12068
13087
|
}
|
|
12069
13088
|
async function runPolicies(client, input) {
|
|
@@ -12227,7 +13246,7 @@ async function resolveProjectEntry(opts) {
|
|
|
12227
13246
|
const cwd = opts.cwd ?? process.cwd();
|
|
12228
13247
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
12229
13248
|
for (const entry2 of entries) {
|
|
12230
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
13249
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path54.default.sep}`)) {
|
|
12231
13250
|
return entry2;
|
|
12232
13251
|
}
|
|
12233
13252
|
}
|
|
@@ -12380,7 +13399,7 @@ async function runSync(opts) {
|
|
|
12380
13399
|
}
|
|
12381
13400
|
|
|
12382
13401
|
// src/cli.ts
|
|
12383
|
-
var
|
|
13402
|
+
var import_types33 = require("@neat.is/types");
|
|
12384
13403
|
function isNpxInvocation() {
|
|
12385
13404
|
if (process.env.npm_command === "exec") return true;
|
|
12386
13405
|
const execpath = process.env.npm_execpath ?? "";
|
|
@@ -12662,7 +13681,7 @@ async function buildPatchSections(services, project) {
|
|
|
12662
13681
|
}
|
|
12663
13682
|
async function runInit(opts) {
|
|
12664
13683
|
const written = [];
|
|
12665
|
-
const stat = await
|
|
13684
|
+
const stat = await import_node_fs35.promises.stat(opts.scanPath).catch(() => null);
|
|
12666
13685
|
if (!stat || !stat.isDirectory()) {
|
|
12667
13686
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
12668
13687
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -12671,13 +13690,13 @@ async function runInit(opts) {
|
|
|
12671
13690
|
printDiscoveryReport(opts, services);
|
|
12672
13691
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
12673
13692
|
const patch = renderPatch(sections);
|
|
12674
|
-
const patchPath =
|
|
13693
|
+
const patchPath = import_node_path55.default.join(opts.scanPath, "neat.patch");
|
|
12675
13694
|
if (opts.dryRun) {
|
|
12676
|
-
await
|
|
13695
|
+
await import_node_fs35.promises.writeFile(patchPath, patch, "utf8");
|
|
12677
13696
|
written.push(patchPath);
|
|
12678
13697
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
12679
|
-
const gitignorePath =
|
|
12680
|
-
const gitignoreExists = await
|
|
13698
|
+
const gitignorePath = import_node_path55.default.join(opts.scanPath, ".gitignore");
|
|
13699
|
+
const gitignoreExists = await import_node_fs35.promises.stat(gitignorePath).then(() => true).catch(() => false);
|
|
12681
13700
|
const verb = gitignoreExists ? "append" : "create";
|
|
12682
13701
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
12683
13702
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -12688,9 +13707,9 @@ async function runInit(opts) {
|
|
|
12688
13707
|
const graph = getGraph(graphKey);
|
|
12689
13708
|
const projectPaths = pathsForProject(
|
|
12690
13709
|
graphKey,
|
|
12691
|
-
|
|
13710
|
+
import_node_path55.default.join(opts.scanPath, "neat-out")
|
|
12692
13711
|
);
|
|
12693
|
-
const errorsPath =
|
|
13712
|
+
const errorsPath = import_node_path55.default.join(import_node_path55.default.dirname(opts.outPath), import_node_path55.default.basename(projectPaths.errorsPath));
|
|
12694
13713
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
12695
13714
|
await saveGraphToDisk(graph, opts.outPath);
|
|
12696
13715
|
written.push(opts.outPath);
|
|
@@ -12769,7 +13788,7 @@ async function runInit(opts) {
|
|
|
12769
13788
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
12770
13789
|
}
|
|
12771
13790
|
} else {
|
|
12772
|
-
await
|
|
13791
|
+
await import_node_fs35.promises.writeFile(patchPath, patch, "utf8");
|
|
12773
13792
|
written.push(patchPath);
|
|
12774
13793
|
}
|
|
12775
13794
|
}
|
|
@@ -12809,9 +13828,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
12809
13828
|
};
|
|
12810
13829
|
function claudeConfigPath() {
|
|
12811
13830
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
12812
|
-
if (override && override.length > 0) return
|
|
13831
|
+
if (override && override.length > 0) return import_node_path55.default.resolve(override);
|
|
12813
13832
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
12814
|
-
return
|
|
13833
|
+
return import_node_path55.default.join(home, ".claude.json");
|
|
12815
13834
|
}
|
|
12816
13835
|
async function runSkill(opts) {
|
|
12817
13836
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -12823,7 +13842,7 @@ async function runSkill(opts) {
|
|
|
12823
13842
|
const target = claudeConfigPath();
|
|
12824
13843
|
let existing = {};
|
|
12825
13844
|
try {
|
|
12826
|
-
existing = JSON.parse(await
|
|
13845
|
+
existing = JSON.parse(await import_node_fs35.promises.readFile(target, "utf8"));
|
|
12827
13846
|
} catch (err) {
|
|
12828
13847
|
if (err.code !== "ENOENT") {
|
|
12829
13848
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -12835,8 +13854,8 @@ async function runSkill(opts) {
|
|
|
12835
13854
|
...existing,
|
|
12836
13855
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
12837
13856
|
};
|
|
12838
|
-
await
|
|
12839
|
-
await
|
|
13857
|
+
await import_node_fs35.promises.mkdir(import_node_path55.default.dirname(target), { recursive: true });
|
|
13858
|
+
await import_node_fs35.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
12840
13859
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
12841
13860
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
12842
13861
|
return { exitCode: 0 };
|
|
@@ -12885,12 +13904,12 @@ async function main() {
|
|
|
12885
13904
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
12886
13905
|
process.exit(2);
|
|
12887
13906
|
}
|
|
12888
|
-
const scanPath =
|
|
13907
|
+
const scanPath = import_node_path55.default.resolve(target);
|
|
12889
13908
|
const projectExplicit = parsed.project !== null;
|
|
12890
|
-
const projectName = projectExplicit ? project :
|
|
13909
|
+
const projectName = projectExplicit ? project : import_node_path55.default.basename(scanPath);
|
|
12891
13910
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
12892
|
-
const fallback = pathsForProject(projectKey,
|
|
12893
|
-
const outPath =
|
|
13911
|
+
const fallback = pathsForProject(projectKey, import_node_path55.default.join(scanPath, "neat-out")).snapshotPath;
|
|
13912
|
+
const outPath = import_node_path55.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
12894
13913
|
const result = await runInit({
|
|
12895
13914
|
scanPath,
|
|
12896
13915
|
outPath,
|
|
@@ -12911,21 +13930,21 @@ async function main() {
|
|
|
12911
13930
|
usage();
|
|
12912
13931
|
process.exit(2);
|
|
12913
13932
|
}
|
|
12914
|
-
const scanPath =
|
|
12915
|
-
const stat = await
|
|
13933
|
+
const scanPath = import_node_path55.default.resolve(target);
|
|
13934
|
+
const stat = await import_node_fs35.promises.stat(scanPath).catch(() => null);
|
|
12916
13935
|
if (!stat || !stat.isDirectory()) {
|
|
12917
13936
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
12918
13937
|
process.exit(2);
|
|
12919
13938
|
}
|
|
12920
|
-
const projectPaths = pathsForProject(project,
|
|
12921
|
-
const outPath =
|
|
12922
|
-
const errorsPath =
|
|
12923
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
13939
|
+
const projectPaths = pathsForProject(project, import_node_path55.default.join(scanPath, "neat-out"));
|
|
13940
|
+
const outPath = import_node_path55.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
13941
|
+
const errorsPath = import_node_path55.default.resolve(
|
|
13942
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path55.default.join(import_node_path55.default.dirname(outPath), import_node_path55.default.basename(projectPaths.errorsPath))
|
|
12924
13943
|
);
|
|
12925
|
-
const staleEventsPath =
|
|
12926
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
13944
|
+
const staleEventsPath = import_node_path55.default.resolve(
|
|
13945
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path55.default.join(import_node_path55.default.dirname(outPath), import_node_path55.default.basename(projectPaths.staleEventsPath))
|
|
12927
13946
|
);
|
|
12928
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
13947
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path55.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
12929
13948
|
const handle = await startWatch(getGraph(project), {
|
|
12930
13949
|
scanPath,
|
|
12931
13950
|
outPath,
|
|
@@ -13108,11 +14127,11 @@ async function main() {
|
|
|
13108
14127
|
process.exit(1);
|
|
13109
14128
|
}
|
|
13110
14129
|
async function tryOrchestrator(cmd, parsed) {
|
|
13111
|
-
const scanPath =
|
|
13112
|
-
const stat = await
|
|
14130
|
+
const scanPath = import_node_path55.default.resolve(cmd);
|
|
14131
|
+
const stat = await import_node_fs35.promises.stat(scanPath).catch(() => null);
|
|
13113
14132
|
if (!stat || !stat.isDirectory()) return null;
|
|
13114
14133
|
const projectExplicit = parsed.project !== null;
|
|
13115
|
-
const projectName = projectExplicit ? parsed.project :
|
|
14134
|
+
const projectName = projectExplicit ? parsed.project : import_node_path55.default.basename(scanPath);
|
|
13116
14135
|
const result = await runOrchestrator({
|
|
13117
14136
|
scanPath,
|
|
13118
14137
|
project: projectName,
|
|
@@ -13301,10 +14320,10 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
13301
14320
|
const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
13302
14321
|
const out = [];
|
|
13303
14322
|
for (const p of parts) {
|
|
13304
|
-
const r =
|
|
14323
|
+
const r = import_types33.DivergenceTypeSchema.safeParse(p);
|
|
13305
14324
|
if (!r.success) {
|
|
13306
14325
|
console.error(
|
|
13307
|
-
`neat divergences: unknown --type "${p}". allowed: ${
|
|
14326
|
+
`neat divergences: unknown --type "${p}". allowed: ${import_types33.DivergenceTypeSchema.options.join(", ")}`
|
|
13308
14327
|
);
|
|
13309
14328
|
return 2;
|
|
13310
14329
|
}
|