@neat.is/core 0.5.3 → 0.6.0
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-X2AMX3QZ.js → chunk-5PVQJLPR.js} +272 -13
- package/dist/chunk-5PVQJLPR.js.map +1 -0
- package/dist/{chunk-CFDPIMRP.js → chunk-BZ3AJVAC.js} +9 -3
- package/dist/chunk-BZ3AJVAC.js.map +1 -0
- package/dist/{chunk-GJHEZC5K.js → chunk-CS4GHQO3.js} +458 -111
- package/dist/chunk-CS4GHQO3.js.map +1 -0
- package/dist/{chunk-I72HTUOG.js → chunk-MVINCLQM.js} +2 -2
- package/dist/cli.cjs +1190 -458
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +140 -25
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +849 -286
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +858 -295
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-IDMIH6ZY.js → otel-grpc-XS45HBET.js} +3 -3
- package/dist/server.cjs +540 -173
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-CFDPIMRP.js.map +0 -1
- package/dist/chunk-GJHEZC5K.js.map +0 -1
- package/dist/chunk-X2AMX3QZ.js.map +0 -1
- /package/dist/{chunk-I72HTUOG.js.map → chunk-MVINCLQM.js.map} +0 -0
- /package/dist/{otel-grpc-IDMIH6ZY.js.map → otel-grpc-XS45HBET.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -60,8 +60,8 @@ function mountBearerAuth(app, opts) {
|
|
|
60
60
|
]);
|
|
61
61
|
const publicRead = opts.publicRead === true;
|
|
62
62
|
app.addHook("preHandler", (req, reply, done) => {
|
|
63
|
-
const
|
|
64
|
-
if (exactUnauthPaths.has(
|
|
63
|
+
const path52 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
64
|
+
if (exactUnauthPaths.has(path52) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path52)) {
|
|
65
65
|
done();
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
@@ -193,8 +193,8 @@ function reshapeGrpcRequest(req) {
|
|
|
193
193
|
};
|
|
194
194
|
}
|
|
195
195
|
function resolveProtoRoot() {
|
|
196
|
-
const here =
|
|
197
|
-
return
|
|
196
|
+
const here = import_node_path48.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
197
|
+
return import_node_path48.default.resolve(here, "..", "proto");
|
|
198
198
|
}
|
|
199
199
|
function loadTraceService() {
|
|
200
200
|
const protoRoot = resolveProtoRoot();
|
|
@@ -262,13 +262,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
262
262
|
})
|
|
263
263
|
};
|
|
264
264
|
}
|
|
265
|
-
var import_node_url,
|
|
265
|
+
var import_node_url, import_node_path48, import_node_crypto2, grpc, protoLoader;
|
|
266
266
|
var init_otel_grpc = __esm({
|
|
267
267
|
"src/otel-grpc.ts"() {
|
|
268
268
|
"use strict";
|
|
269
269
|
init_cjs_shims();
|
|
270
270
|
import_node_url = require("url");
|
|
271
|
-
|
|
271
|
+
import_node_path48 = __toESM(require("path"), 1);
|
|
272
272
|
import_node_crypto2 = require("crypto");
|
|
273
273
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
274
274
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -341,6 +341,11 @@ function pickEnv(spanAttrs, resourceAttrs) {
|
|
|
341
341
|
}
|
|
342
342
|
return ENV_FALLBACK;
|
|
343
343
|
}
|
|
344
|
+
function normalizeDbSystem(attrs) {
|
|
345
|
+
const raw = attrs["db.system"];
|
|
346
|
+
if (typeof raw !== "string") return void 0;
|
|
347
|
+
return raw === "mongoose" ? "mongodb" : raw;
|
|
348
|
+
}
|
|
344
349
|
function messagingDestinationOf(attrs) {
|
|
345
350
|
for (const key of ["messaging.destination.name", "messaging.destination"]) {
|
|
346
351
|
const v = attrs[key];
|
|
@@ -361,8 +366,8 @@ function websocketChannelPathOf(attrs) {
|
|
|
361
366
|
const v = attrs[key];
|
|
362
367
|
if (typeof v === "string" && v.length > 0) {
|
|
363
368
|
const q = v.indexOf("?");
|
|
364
|
-
const
|
|
365
|
-
if (
|
|
369
|
+
const path52 = q === -1 ? v : v.slice(0, q);
|
|
370
|
+
if (path52.length > 0) return path52;
|
|
366
371
|
}
|
|
367
372
|
}
|
|
368
373
|
return void 0;
|
|
@@ -395,8 +400,9 @@ function parseOtlpRequest(body) {
|
|
|
395
400
|
durationNanos: durationNanos(span.startTimeUnixNano, span.endTimeUnixNano),
|
|
396
401
|
env: pickEnv(attrs, resourceAttrs),
|
|
397
402
|
attributes: attrs,
|
|
398
|
-
dbSystem:
|
|
403
|
+
dbSystem: normalizeDbSystem(attrs),
|
|
399
404
|
dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
|
|
405
|
+
dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
|
|
400
406
|
messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
|
|
401
407
|
messagingDestination: messagingDestinationOf(attrs),
|
|
402
408
|
graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
|
|
@@ -416,10 +422,10 @@ function parseOtlpRequest(body) {
|
|
|
416
422
|
return out;
|
|
417
423
|
}
|
|
418
424
|
function loadProtoRoot() {
|
|
419
|
-
const here =
|
|
420
|
-
const protoRoot =
|
|
425
|
+
const here = import_node_path49.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
426
|
+
const protoRoot = import_node_path49.default.resolve(here, "..", "proto");
|
|
421
427
|
const root = new import_protobufjs.default.Root();
|
|
422
|
-
root.resolvePath = (_origin, target) =>
|
|
428
|
+
root.resolvePath = (_origin, target) => import_node_path49.default.resolve(protoRoot, target);
|
|
423
429
|
root.loadSync(
|
|
424
430
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
425
431
|
{ keepCase: true }
|
|
@@ -655,12 +661,12 @@ function logSpanHandler(span) {
|
|
|
655
661
|
`otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
|
|
656
662
|
);
|
|
657
663
|
}
|
|
658
|
-
var
|
|
664
|
+
var import_node_path49, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
|
|
659
665
|
var init_otel = __esm({
|
|
660
666
|
"src/otel.ts"() {
|
|
661
667
|
"use strict";
|
|
662
668
|
init_cjs_shims();
|
|
663
|
-
|
|
669
|
+
import_node_path49 = __toESM(require("path"), 1);
|
|
664
670
|
import_node_url2 = require("url");
|
|
665
671
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
666
672
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -1250,19 +1256,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1250
1256
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1251
1257
|
let best = { path: [start], edges: [] };
|
|
1252
1258
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1253
|
-
function step(node,
|
|
1254
|
-
if (
|
|
1255
|
-
best = { path: [...
|
|
1259
|
+
function step(node, path52, edges) {
|
|
1260
|
+
if (path52.length > best.path.length) {
|
|
1261
|
+
best = { path: [...path52], edges: [...edges] };
|
|
1256
1262
|
}
|
|
1257
|
-
if (
|
|
1263
|
+
if (path52.length - 1 >= maxDepth) return;
|
|
1258
1264
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1259
1265
|
for (const [srcId, edge] of incoming) {
|
|
1260
1266
|
if (visited.has(srcId)) continue;
|
|
1261
1267
|
visited.add(srcId);
|
|
1262
|
-
|
|
1268
|
+
path52.push(srcId);
|
|
1263
1269
|
edges.push(edge);
|
|
1264
|
-
step(srcId,
|
|
1265
|
-
|
|
1270
|
+
step(srcId, path52, edges);
|
|
1271
|
+
path52.pop();
|
|
1266
1272
|
edges.pop();
|
|
1267
1273
|
visited.delete(srcId);
|
|
1268
1274
|
}
|
|
@@ -1456,26 +1462,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
|
|
|
1456
1462
|
return best;
|
|
1457
1463
|
}
|
|
1458
1464
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1459
|
-
const
|
|
1465
|
+
const path52 = [originServiceId];
|
|
1460
1466
|
const edges = [];
|
|
1461
1467
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1462
1468
|
let current = originServiceId;
|
|
1463
1469
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1464
1470
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1465
1471
|
if (!hop) break;
|
|
1466
|
-
|
|
1472
|
+
path52.push(hop.nextService);
|
|
1467
1473
|
edges.push(hop.edge);
|
|
1468
1474
|
visited.add(hop.nextService);
|
|
1469
1475
|
current = hop.nextService;
|
|
1470
1476
|
}
|
|
1471
1477
|
if (edges.length === 0) return null;
|
|
1472
|
-
return { path:
|
|
1478
|
+
return { path: path52, edges, culprit: current };
|
|
1473
1479
|
}
|
|
1474
1480
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1475
1481
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1476
1482
|
if (!chain) return null;
|
|
1477
1483
|
const culprit = chain.culprit;
|
|
1478
|
-
const
|
|
1484
|
+
const path52 = [...chain.path];
|
|
1479
1485
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1480
1486
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1481
1487
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1483,14 +1489,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1483
1489
|
if (loc) {
|
|
1484
1490
|
let rootCauseNode = culprit;
|
|
1485
1491
|
if (loc.fileNode) {
|
|
1486
|
-
|
|
1492
|
+
path52.push(loc.fileNode);
|
|
1487
1493
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1488
1494
|
rootCauseNode = loc.fileNode;
|
|
1489
1495
|
}
|
|
1490
1496
|
return import_types.RootCauseResultSchema.parse({
|
|
1491
1497
|
rootCauseNode,
|
|
1492
1498
|
rootCauseReason: loc.rootCauseReason,
|
|
1493
|
-
traversalPath:
|
|
1499
|
+
traversalPath: path52,
|
|
1494
1500
|
edgeProvenances,
|
|
1495
1501
|
confidence,
|
|
1496
1502
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1502,7 +1508,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1502
1508
|
return import_types.RootCauseResultSchema.parse({
|
|
1503
1509
|
rootCauseNode: culprit,
|
|
1504
1510
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1505
|
-
traversalPath:
|
|
1511
|
+
traversalPath: path52,
|
|
1506
1512
|
edgeProvenances,
|
|
1507
1513
|
confidence,
|
|
1508
1514
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -2929,6 +2935,18 @@ async function handleSpan(ctx, span) {
|
|
|
2929
2935
|
callSiteEvidence
|
|
2930
2936
|
);
|
|
2931
2937
|
if (result) affectedNode = targetId;
|
|
2938
|
+
if (span.dbSystem === "mongodb" && span.dbCollection) {
|
|
2939
|
+
const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
|
|
2940
|
+
upsertObservedEdge(
|
|
2941
|
+
ctx.graph,
|
|
2942
|
+
import_types3.EdgeType.CALLS,
|
|
2943
|
+
observedSource(),
|
|
2944
|
+
collectionId,
|
|
2945
|
+
ts,
|
|
2946
|
+
isError,
|
|
2947
|
+
callSiteEvidence
|
|
2948
|
+
);
|
|
2949
|
+
}
|
|
2932
2950
|
}
|
|
2933
2951
|
} else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
|
|
2934
2952
|
const targetId = ensureMessagingDestinationNode(
|
|
@@ -5776,7 +5794,7 @@ async function addGrpcMethods(graph, services) {
|
|
|
5776
5794
|
|
|
5777
5795
|
// src/extract/calls/index.ts
|
|
5778
5796
|
init_cjs_shims();
|
|
5779
|
-
var
|
|
5797
|
+
var import_types21 = require("@neat.is/types");
|
|
5780
5798
|
|
|
5781
5799
|
// src/extract/calls/http.ts
|
|
5782
5800
|
init_cjs_shims();
|
|
@@ -6489,15 +6507,311 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
6489
6507
|
return out;
|
|
6490
6508
|
}
|
|
6491
6509
|
|
|
6510
|
+
// src/extract/calls/mongoose.ts
|
|
6511
|
+
init_cjs_shims();
|
|
6512
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
6513
|
+
var import_types20 = require("@neat.is/types");
|
|
6514
|
+
var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
|
|
6515
|
+
var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
|
|
6516
|
+
var PLURALIZE_DISABLED_RE = /\bpluralize\s*\(\s*(?:null|false)\s*\)/;
|
|
6517
|
+
var UNCOUNTABLES = /* @__PURE__ */ new Set([
|
|
6518
|
+
"advice",
|
|
6519
|
+
"energy",
|
|
6520
|
+
"excretion",
|
|
6521
|
+
"digestion",
|
|
6522
|
+
"cooperation",
|
|
6523
|
+
"health",
|
|
6524
|
+
"justice",
|
|
6525
|
+
"labour",
|
|
6526
|
+
"machinery",
|
|
6527
|
+
"equipment",
|
|
6528
|
+
"information",
|
|
6529
|
+
"pollution",
|
|
6530
|
+
"sewage",
|
|
6531
|
+
"paper",
|
|
6532
|
+
"money",
|
|
6533
|
+
"species",
|
|
6534
|
+
"series",
|
|
6535
|
+
"rain",
|
|
6536
|
+
"rice",
|
|
6537
|
+
"fish",
|
|
6538
|
+
"sheep",
|
|
6539
|
+
"moose",
|
|
6540
|
+
"deer",
|
|
6541
|
+
"news",
|
|
6542
|
+
"expertise",
|
|
6543
|
+
"status",
|
|
6544
|
+
"media"
|
|
6545
|
+
]);
|
|
6546
|
+
var PLURALIZE_RULES = [
|
|
6547
|
+
[/human$/gi, "humans"],
|
|
6548
|
+
[/(m)an$/gi, "$1en"],
|
|
6549
|
+
[/(pe)rson$/gi, "$1ople"],
|
|
6550
|
+
[/(child)$/gi, "$1ren"],
|
|
6551
|
+
[/^(ox)$/gi, "$1en"],
|
|
6552
|
+
[/(ax|test)is$/gi, "$1es"],
|
|
6553
|
+
[/(octop|vir)us$/gi, "$1i"],
|
|
6554
|
+
[/(alias|status)$/gi, "$1es"],
|
|
6555
|
+
[/(bu)s$/gi, "$1ses"],
|
|
6556
|
+
[/(buffal|tomat|potat)o$/gi, "$1oes"],
|
|
6557
|
+
[/([ti])um$/gi, "$1a"],
|
|
6558
|
+
[/sis$/gi, "ses"],
|
|
6559
|
+
[/(?:([^f])fe|([lr])f)$/gi, "$1$2ves"],
|
|
6560
|
+
[/(hive)$/gi, "$1s"],
|
|
6561
|
+
[/([^aeiouy]|qu)y$/gi, "$1ies"],
|
|
6562
|
+
[/(x|ch|ss|sh)$/gi, "$1es"],
|
|
6563
|
+
[/(matr|vert|ind)ix|ex$/gi, "$1ices"],
|
|
6564
|
+
[/([m|l])ouse$/gi, "$1ice"],
|
|
6565
|
+
[/(kn|w|l)ife$/gi, "$1ives"],
|
|
6566
|
+
[/(quiz)$/gi, "$1zes"],
|
|
6567
|
+
[/s$/gi, "s"],
|
|
6568
|
+
[/([^a-z])$/, "$1"],
|
|
6569
|
+
[/$/gi, "s"]
|
|
6570
|
+
];
|
|
6571
|
+
function pluralizeCollection(name) {
|
|
6572
|
+
const str = name.toLowerCase();
|
|
6573
|
+
if (UNCOUNTABLES.has(str)) return str;
|
|
6574
|
+
for (const [re, repl] of PLURALIZE_RULES) {
|
|
6575
|
+
if (str.match(re)) return str.replace(re, repl);
|
|
6576
|
+
}
|
|
6577
|
+
return str;
|
|
6578
|
+
}
|
|
6579
|
+
var MODEL_METHODS = [
|
|
6580
|
+
"find",
|
|
6581
|
+
"findOne",
|
|
6582
|
+
"findById",
|
|
6583
|
+
"findByIdAndUpdate",
|
|
6584
|
+
"findByIdAndDelete",
|
|
6585
|
+
"findOneAndUpdate",
|
|
6586
|
+
"findOneAndDelete",
|
|
6587
|
+
"findOneAndReplace",
|
|
6588
|
+
"countDocuments",
|
|
6589
|
+
"estimatedDocumentCount",
|
|
6590
|
+
"distinct",
|
|
6591
|
+
"aggregate",
|
|
6592
|
+
"exists",
|
|
6593
|
+
"where",
|
|
6594
|
+
"populate",
|
|
6595
|
+
"watch",
|
|
6596
|
+
"hydrate",
|
|
6597
|
+
"create",
|
|
6598
|
+
"insertMany",
|
|
6599
|
+
"insertOne",
|
|
6600
|
+
"updateOne",
|
|
6601
|
+
"updateMany",
|
|
6602
|
+
"replaceOne",
|
|
6603
|
+
"deleteOne",
|
|
6604
|
+
"deleteMany",
|
|
6605
|
+
"bulkWrite",
|
|
6606
|
+
"bulkSave",
|
|
6607
|
+
"save",
|
|
6608
|
+
// pre-v7, removed in current mongoose but present in older codebases:
|
|
6609
|
+
"count",
|
|
6610
|
+
"update",
|
|
6611
|
+
"remove",
|
|
6612
|
+
"findOneAndRemove",
|
|
6613
|
+
"findByIdAndRemove"
|
|
6614
|
+
];
|
|
6615
|
+
var MODEL_METHODS_ALT = MODEL_METHODS.join("|");
|
|
6616
|
+
function schemaCollectionVars(content) {
|
|
6617
|
+
const out = /* @__PURE__ */ new Map();
|
|
6618
|
+
const re = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+(?:mongoose\s*\.\s*)?Schema\s*\(/g;
|
|
6619
|
+
let m;
|
|
6620
|
+
while ((m = re.exec(content)) !== null) {
|
|
6621
|
+
const windowText = content.slice(m.index, m.index + 2e3);
|
|
6622
|
+
const opt = /\bcollection\s*:\s*['"`]([\w.-]+)['"`]/.exec(windowText);
|
|
6623
|
+
if (opt) out.set(m[1], opt[1]);
|
|
6624
|
+
}
|
|
6625
|
+
return out;
|
|
6626
|
+
}
|
|
6627
|
+
function resolveModel(modelName, rest, schemaColl, pluralizeOn) {
|
|
6628
|
+
const trimmed = rest.trim();
|
|
6629
|
+
const literalThird = /,\s*['"`]([\w.-]+)['"`]\s*$/.exec(trimmed);
|
|
6630
|
+
if (literalThird) return { name: literalThird[1], kind: "mongodb-collection" };
|
|
6631
|
+
const firstIdent = /^([A-Za-z_$][\w$]*)/.exec(trimmed)?.[1];
|
|
6632
|
+
if (firstIdent && schemaColl.has(firstIdent)) {
|
|
6633
|
+
return { name: schemaColl.get(firstIdent), kind: "mongodb-collection" };
|
|
6634
|
+
}
|
|
6635
|
+
if (/,\s*[A-Za-z_$][\w$]*\s*$/.test(trimmed)) {
|
|
6636
|
+
return { name: modelName, kind: "mongodb-model" };
|
|
6637
|
+
}
|
|
6638
|
+
return { name: pluralizeOn ? pluralizeCollection(modelName) : modelName, kind: "mongodb-collection" };
|
|
6639
|
+
}
|
|
6640
|
+
function collectModelDefs(content, pluralizeOn) {
|
|
6641
|
+
const schemaColl = schemaCollectionVars(content);
|
|
6642
|
+
const out = [];
|
|
6643
|
+
const re = /(?:(?:const|let|var)\s+(\w+)\s*=\s*)?(?:await\s+)?(?:\b\w+\s*\.\s*)?\bmodel\s*\(\s*['"`]([\w$]+)['"`]\s*(?:,\s*([\s\S]{0,300}?))?\)/g;
|
|
6644
|
+
let m;
|
|
6645
|
+
while ((m = re.exec(content)) !== null) {
|
|
6646
|
+
out.push({
|
|
6647
|
+
modelName: m[2],
|
|
6648
|
+
varName: m[1] ?? null,
|
|
6649
|
+
resolved: resolveModel(m[2], m[3] ?? "", schemaColl, pluralizeOn),
|
|
6650
|
+
matchText: m[0]
|
|
6651
|
+
});
|
|
6652
|
+
}
|
|
6653
|
+
return out;
|
|
6654
|
+
}
|
|
6655
|
+
function endpoint(r, file, serviceDir, matchText) {
|
|
6656
|
+
const line = lineOf(file.content, matchText);
|
|
6657
|
+
return {
|
|
6658
|
+
infraId: (0, import_types20.infraId)(r.kind, r.name),
|
|
6659
|
+
name: r.name,
|
|
6660
|
+
kind: r.kind,
|
|
6661
|
+
edgeType: "CALLS",
|
|
6662
|
+
confidenceKind: "verified-call-site",
|
|
6663
|
+
evidence: { file: import_node_path31.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
6664
|
+
};
|
|
6665
|
+
}
|
|
6666
|
+
function mongooseEndpointsFromFile(file, serviceDir) {
|
|
6667
|
+
const hasMongoose = MONGOOSE_IMPORT_RE.test(file.content);
|
|
6668
|
+
const hasMongodb = MONGODB_IMPORT_RE.test(file.content);
|
|
6669
|
+
if (!hasMongoose && !hasMongodb) return [];
|
|
6670
|
+
const content = file.content;
|
|
6671
|
+
const out = [];
|
|
6672
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6673
|
+
const push = (r, matchText) => {
|
|
6674
|
+
const key = `${r.kind}/${r.name}`;
|
|
6675
|
+
if (seen.has(key)) return;
|
|
6676
|
+
seen.add(key);
|
|
6677
|
+
out.push(endpoint(r, file, serviceDir, matchText));
|
|
6678
|
+
};
|
|
6679
|
+
if (hasMongoose) {
|
|
6680
|
+
const pluralizeOn = !PLURALIZE_DISABLED_RE.test(content);
|
|
6681
|
+
for (const def of collectModelDefs(content, pluralizeOn)) push(def.resolved, def.matchText);
|
|
6682
|
+
}
|
|
6683
|
+
const collRe = /\.\s*collection\s*\(\s*['"`]([\w.$-]+)['"`]\s*\)/g;
|
|
6684
|
+
let c;
|
|
6685
|
+
while ((c = collRe.exec(content)) !== null) {
|
|
6686
|
+
push({ name: c[1], kind: "mongodb-collection" }, c[0]);
|
|
6687
|
+
}
|
|
6688
|
+
return out;
|
|
6689
|
+
}
|
|
6690
|
+
function parseDestructure(inner, specifier, out) {
|
|
6691
|
+
for (const raw of inner.split(",")) {
|
|
6692
|
+
const part = raw.trim();
|
|
6693
|
+
if (!part) continue;
|
|
6694
|
+
const asM = /^(\w+)\s+as\s+(\w+)$/.exec(part);
|
|
6695
|
+
const colonM = /^(\w+)\s*:\s*(\w+)$/.exec(part);
|
|
6696
|
+
if (asM) out.push({ local: asM[2], kind: "named", exportName: asM[1], specifier });
|
|
6697
|
+
else if (colonM) out.push({ local: colonM[2], kind: "named", exportName: colonM[1], specifier });
|
|
6698
|
+
else {
|
|
6699
|
+
const id = /^(\w+)$/.exec(part);
|
|
6700
|
+
if (id) out.push({ local: id[1], kind: "named", exportName: id[1], specifier });
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
}
|
|
6704
|
+
function parseImportBindings(content) {
|
|
6705
|
+
const out = [];
|
|
6706
|
+
const esm = /import\s+([^;'"`\n]+?)\s+from\s*['"`]([^'"`]+)['"`]/g;
|
|
6707
|
+
let m;
|
|
6708
|
+
while ((m = esm.exec(content)) !== null) {
|
|
6709
|
+
const clause = m[1].trim();
|
|
6710
|
+
const spec = m[2];
|
|
6711
|
+
const ns = /^\*\s+as\s+(\w+)$/.exec(clause);
|
|
6712
|
+
if (ns) {
|
|
6713
|
+
out.push({ local: ns[1], kind: "namespace", specifier: spec });
|
|
6714
|
+
continue;
|
|
6715
|
+
}
|
|
6716
|
+
const named = /\{([^}]*)\}/.exec(clause);
|
|
6717
|
+
if (named) parseDestructure(named[1], spec, out);
|
|
6718
|
+
const def = /^(\w+)\s*(?:,|$)/.exec(clause);
|
|
6719
|
+
if (def && !clause.startsWith("{")) out.push({ local: def[1], kind: "default", specifier: spec });
|
|
6720
|
+
}
|
|
6721
|
+
const cjs = /(?:const|let|var)\s+(\{[^}]*\}|\w+)\s*=\s*require\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
|
|
6722
|
+
while ((m = cjs.exec(content)) !== null) {
|
|
6723
|
+
const lhs = m[1];
|
|
6724
|
+
const spec = m[2];
|
|
6725
|
+
if (lhs.startsWith("{")) parseDestructure(lhs.slice(1, -1), spec, out);
|
|
6726
|
+
else out.push({ local: lhs, kind: "whole", specifier: spec });
|
|
6727
|
+
}
|
|
6728
|
+
return out;
|
|
6729
|
+
}
|
|
6730
|
+
function fileExportsOf(content, pluralizeOn) {
|
|
6731
|
+
const defs = collectModelDefs(content, pluralizeOn).filter((d) => d.resolved.kind === "mongodb-collection");
|
|
6732
|
+
if (defs.length === 0) return null;
|
|
6733
|
+
const byVar = /* @__PURE__ */ new Map();
|
|
6734
|
+
for (const d of defs) if (d.varName) byVar.set(d.varName, d.resolved.name);
|
|
6735
|
+
const byName = new Map(byVar);
|
|
6736
|
+
let def;
|
|
6737
|
+
const named = /(?:module\.exports|export\s+default)\s*=\s*(\w+)\b/.exec(content);
|
|
6738
|
+
if (named && byVar.has(named[1])) def = byVar.get(named[1]);
|
|
6739
|
+
if (!def) {
|
|
6740
|
+
const inline = /(?:module\.exports|export\s+default)\s*=\s*(?:await\s+)?(?:\w+\s*\.\s*)?model\s*\(\s*['"`]([\w$]+)['"`]/.exec(content);
|
|
6741
|
+
if (inline) def = pluralizeOn ? pluralizeCollection(inline[1]) : inline[1];
|
|
6742
|
+
}
|
|
6743
|
+
if (!def && byVar.size === 1) def = [...byVar.values()][0];
|
|
6744
|
+
return { byName, ...def ? { default: def } : {} };
|
|
6745
|
+
}
|
|
6746
|
+
async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
6747
|
+
const mongooseFiles = files.filter((f) => MONGOOSE_IMPORT_RE.test(f.content));
|
|
6748
|
+
if (mongooseFiles.length === 0) return [];
|
|
6749
|
+
const pluralizeOn = !files.some((f) => PLURALIZE_DISABLED_RE.test(f.content));
|
|
6750
|
+
const registry = /* @__PURE__ */ new Map();
|
|
6751
|
+
for (const f of mongooseFiles) {
|
|
6752
|
+
const fx = fileExportsOf(f.content, pluralizeOn);
|
|
6753
|
+
if (fx) registry.set(toPosix2(import_node_path31.default.relative(serviceDir, f.path)), fx);
|
|
6754
|
+
}
|
|
6755
|
+
if (registry.size === 0) return [];
|
|
6756
|
+
const out = [];
|
|
6757
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6758
|
+
for (const f of files) {
|
|
6759
|
+
const bindings = parseImportBindings(f.content);
|
|
6760
|
+
if (bindings.length === 0) continue;
|
|
6761
|
+
const directColl = /* @__PURE__ */ new Map();
|
|
6762
|
+
const nsExports = /* @__PURE__ */ new Map();
|
|
6763
|
+
for (const b of bindings) {
|
|
6764
|
+
const resolvedRel = await resolveJsImport(b.specifier, import_node_path31.default.dirname(f.path), serviceDir, null);
|
|
6765
|
+
if (!resolvedRel) continue;
|
|
6766
|
+
const fx = registry.get(resolvedRel);
|
|
6767
|
+
if (!fx) continue;
|
|
6768
|
+
if (b.kind === "named" && b.exportName) {
|
|
6769
|
+
const coll = fx.byName.get(b.exportName);
|
|
6770
|
+
if (coll) directColl.set(b.local, coll);
|
|
6771
|
+
} else if (b.kind === "default") {
|
|
6772
|
+
if (fx.default) directColl.set(b.local, fx.default);
|
|
6773
|
+
} else {
|
|
6774
|
+
if (fx.default) directColl.set(b.local, fx.default);
|
|
6775
|
+
nsExports.set(b.local, fx);
|
|
6776
|
+
}
|
|
6777
|
+
}
|
|
6778
|
+
if (directColl.size === 0 && nsExports.size === 0) continue;
|
|
6779
|
+
const localDefs = new Set(
|
|
6780
|
+
collectModelDefs(f.content, pluralizeOn).map((d) => d.varName).filter((v) => v !== null)
|
|
6781
|
+
);
|
|
6782
|
+
const emit = (collection, matchText) => {
|
|
6783
|
+
const key = `${f.path}::${collection}`;
|
|
6784
|
+
if (seen.has(key)) return;
|
|
6785
|
+
seen.add(key);
|
|
6786
|
+
out.push(endpoint({ name: collection, kind: "mongodb-collection" }, f, serviceDir, matchText));
|
|
6787
|
+
};
|
|
6788
|
+
for (const [local, collection] of directColl) {
|
|
6789
|
+
if (localDefs.has(local)) continue;
|
|
6790
|
+
const qre = new RegExp(`\\b${local}\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
|
|
6791
|
+
let qm;
|
|
6792
|
+
while ((qm = qre.exec(f.content)) !== null) emit(collection, qm[0]);
|
|
6793
|
+
}
|
|
6794
|
+
for (const [local, fx] of nsExports) {
|
|
6795
|
+
const qre = new RegExp(`\\b${local}\\s*\\.\\s*(\\w+)\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
|
|
6796
|
+
let qm;
|
|
6797
|
+
while ((qm = qre.exec(f.content)) !== null) {
|
|
6798
|
+
const coll = fx.byName.get(qm[1]);
|
|
6799
|
+
if (coll) emit(coll, qm[0]);
|
|
6800
|
+
}
|
|
6801
|
+
}
|
|
6802
|
+
}
|
|
6803
|
+
return out;
|
|
6804
|
+
}
|
|
6805
|
+
|
|
6492
6806
|
// src/extract/calls/index.ts
|
|
6493
6807
|
function edgeTypeFromEndpoint(ep) {
|
|
6494
6808
|
switch (ep.edgeType) {
|
|
6495
6809
|
case "PUBLISHES_TO":
|
|
6496
|
-
return
|
|
6810
|
+
return import_types21.EdgeType.PUBLISHES_TO;
|
|
6497
6811
|
case "CONSUMES_FROM":
|
|
6498
|
-
return
|
|
6812
|
+
return import_types21.EdgeType.CONSUMES_FROM;
|
|
6499
6813
|
default:
|
|
6500
|
-
return
|
|
6814
|
+
return import_types21.EdgeType.CALLS;
|
|
6501
6815
|
}
|
|
6502
6816
|
}
|
|
6503
6817
|
function isAwsKind(kind) {
|
|
@@ -6509,23 +6823,27 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6509
6823
|
for (const service of services) {
|
|
6510
6824
|
const files = await loadSourceFiles(service.dir);
|
|
6511
6825
|
const endpoints = [];
|
|
6826
|
+
const maskedFiles = [];
|
|
6512
6827
|
for (const file of files) {
|
|
6513
6828
|
if (isTestPath(file.path)) continue;
|
|
6514
6829
|
const masked = maskCommentsInSource(file.content);
|
|
6515
6830
|
const maskedFile = { path: file.path, content: masked };
|
|
6831
|
+
maskedFiles.push(maskedFile);
|
|
6516
6832
|
endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir));
|
|
6517
6833
|
endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
|
|
6518
6834
|
endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
|
|
6519
6835
|
endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
|
|
6520
6836
|
endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
|
|
6837
|
+
endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
|
|
6521
6838
|
}
|
|
6839
|
+
endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
|
|
6522
6840
|
if (endpoints.length === 0) continue;
|
|
6523
6841
|
const seenEdges = /* @__PURE__ */ new Set();
|
|
6524
6842
|
for (const ep of endpoints) {
|
|
6525
6843
|
if (!graph.hasNode(ep.infraId)) {
|
|
6526
6844
|
const node = {
|
|
6527
6845
|
id: ep.infraId,
|
|
6528
|
-
type:
|
|
6846
|
+
type: import_types21.NodeType.InfraNode,
|
|
6529
6847
|
name: ep.name,
|
|
6530
6848
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
6531
6849
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -6537,7 +6855,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6537
6855
|
nodesAdded++;
|
|
6538
6856
|
}
|
|
6539
6857
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
6540
|
-
const confidence = (0,
|
|
6858
|
+
const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
|
|
6541
6859
|
const relFile = toPosix2(ep.evidence.file);
|
|
6542
6860
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
6543
6861
|
graph,
|
|
@@ -6547,7 +6865,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6547
6865
|
);
|
|
6548
6866
|
nodesAdded += n;
|
|
6549
6867
|
edgesAdded += e;
|
|
6550
|
-
if (!(0,
|
|
6868
|
+
if (!(0, import_types21.passesExtractedFloor)(confidence)) {
|
|
6551
6869
|
noteExtractedDropped({
|
|
6552
6870
|
source: fileNodeId,
|
|
6553
6871
|
target: ep.infraId,
|
|
@@ -6567,7 +6885,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6567
6885
|
source: fileNodeId,
|
|
6568
6886
|
target: ep.infraId,
|
|
6569
6887
|
type: edgeType,
|
|
6570
|
-
provenance:
|
|
6888
|
+
provenance: import_types21.Provenance.EXTRACTED,
|
|
6571
6889
|
confidence,
|
|
6572
6890
|
evidence: ep.evidence
|
|
6573
6891
|
};
|
|
@@ -6593,16 +6911,16 @@ init_cjs_shims();
|
|
|
6593
6911
|
|
|
6594
6912
|
// src/extract/infra/docker-compose.ts
|
|
6595
6913
|
init_cjs_shims();
|
|
6596
|
-
var
|
|
6597
|
-
var
|
|
6914
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
6915
|
+
var import_types23 = require("@neat.is/types");
|
|
6598
6916
|
|
|
6599
6917
|
// src/extract/infra/shared.ts
|
|
6600
6918
|
init_cjs_shims();
|
|
6601
|
-
var
|
|
6919
|
+
var import_types22 = require("@neat.is/types");
|
|
6602
6920
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
6603
6921
|
return {
|
|
6604
|
-
id: (0,
|
|
6605
|
-
type:
|
|
6922
|
+
id: (0, import_types22.infraId)(kind, name),
|
|
6923
|
+
type: import_types22.NodeType.InfraNode,
|
|
6606
6924
|
name,
|
|
6607
6925
|
provider,
|
|
6608
6926
|
kind,
|
|
@@ -6646,8 +6964,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
|
|
|
6646
6964
|
source: anchorId,
|
|
6647
6965
|
target: node.id,
|
|
6648
6966
|
type: edgeType,
|
|
6649
|
-
provenance:
|
|
6650
|
-
confidence: (0,
|
|
6967
|
+
provenance: import_types22.Provenance.EXTRACTED,
|
|
6968
|
+
confidence: (0, import_types22.confidenceForExtracted)("structural"),
|
|
6651
6969
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
6652
6970
|
};
|
|
6653
6971
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -6664,7 +6982,7 @@ function dependsOnList(value) {
|
|
|
6664
6982
|
}
|
|
6665
6983
|
function serviceNameToServiceNode(name, services) {
|
|
6666
6984
|
for (const s of services) {
|
|
6667
|
-
if (s.node.name === name ||
|
|
6985
|
+
if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
|
|
6668
6986
|
}
|
|
6669
6987
|
return null;
|
|
6670
6988
|
}
|
|
@@ -6673,7 +6991,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
6673
6991
|
let edgesAdded = 0;
|
|
6674
6992
|
let composePath = null;
|
|
6675
6993
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
6676
|
-
const abs =
|
|
6994
|
+
const abs = import_node_path32.default.join(scanPath, name);
|
|
6677
6995
|
if (await exists(abs)) {
|
|
6678
6996
|
composePath = abs;
|
|
6679
6997
|
break;
|
|
@@ -6686,13 +7004,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
6686
7004
|
} catch (err) {
|
|
6687
7005
|
recordExtractionError(
|
|
6688
7006
|
"infra docker-compose",
|
|
6689
|
-
|
|
7007
|
+
import_node_path32.default.relative(scanPath, composePath),
|
|
6690
7008
|
err
|
|
6691
7009
|
);
|
|
6692
7010
|
return { nodesAdded, edgesAdded };
|
|
6693
7011
|
}
|
|
6694
7012
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
6695
|
-
const evidenceFile =
|
|
7013
|
+
const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
|
|
6696
7014
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
6697
7015
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
6698
7016
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -6714,15 +7032,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
6714
7032
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
6715
7033
|
const targetId = composeNameToNodeId.get(dep);
|
|
6716
7034
|
if (!targetId) continue;
|
|
6717
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
7035
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
|
|
6718
7036
|
if (graph.hasEdge(edgeId)) continue;
|
|
6719
7037
|
const edge = {
|
|
6720
7038
|
id: edgeId,
|
|
6721
7039
|
source: sourceId,
|
|
6722
7040
|
target: targetId,
|
|
6723
|
-
type:
|
|
6724
|
-
provenance:
|
|
6725
|
-
confidence: (0,
|
|
7041
|
+
type: import_types23.EdgeType.DEPENDS_ON,
|
|
7042
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
7043
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
6726
7044
|
evidence: { file: evidenceFile }
|
|
6727
7045
|
};
|
|
6728
7046
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -6734,9 +7052,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
6734
7052
|
|
|
6735
7053
|
// src/extract/infra/dockerfile.ts
|
|
6736
7054
|
init_cjs_shims();
|
|
6737
|
-
var
|
|
7055
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
6738
7056
|
var import_node_fs16 = require("fs");
|
|
6739
|
-
var
|
|
7057
|
+
var import_types24 = require("@neat.is/types");
|
|
6740
7058
|
function readDockerfile(content) {
|
|
6741
7059
|
let image = null;
|
|
6742
7060
|
const ports = [];
|
|
@@ -6765,7 +7083,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6765
7083
|
let nodesAdded = 0;
|
|
6766
7084
|
let edgesAdded = 0;
|
|
6767
7085
|
for (const service of services) {
|
|
6768
|
-
const dockerfilePath =
|
|
7086
|
+
const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
|
|
6769
7087
|
if (!await exists(dockerfilePath)) continue;
|
|
6770
7088
|
let content;
|
|
6771
7089
|
try {
|
|
@@ -6773,7 +7091,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6773
7091
|
} catch (err) {
|
|
6774
7092
|
recordExtractionError(
|
|
6775
7093
|
"infra dockerfile",
|
|
6776
|
-
|
|
7094
|
+
import_node_path33.default.relative(scanPath, dockerfilePath),
|
|
6777
7095
|
err
|
|
6778
7096
|
);
|
|
6779
7097
|
continue;
|
|
@@ -6785,8 +7103,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6785
7103
|
graph.addNode(node.id, node);
|
|
6786
7104
|
nodesAdded++;
|
|
6787
7105
|
}
|
|
6788
|
-
const relDockerfile = toPosix2(
|
|
6789
|
-
const evidenceFile = toPosix2(
|
|
7106
|
+
const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
|
|
7107
|
+
const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
|
|
6790
7108
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
6791
7109
|
graph,
|
|
6792
7110
|
service.pkg.name,
|
|
@@ -6795,15 +7113,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6795
7113
|
);
|
|
6796
7114
|
nodesAdded += fn;
|
|
6797
7115
|
edgesAdded += fe;
|
|
6798
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id,
|
|
7116
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
|
|
6799
7117
|
if (!graph.hasEdge(edgeId)) {
|
|
6800
7118
|
const edge = {
|
|
6801
7119
|
id: edgeId,
|
|
6802
7120
|
source: fileNodeId,
|
|
6803
7121
|
target: node.id,
|
|
6804
|
-
type:
|
|
6805
|
-
provenance:
|
|
6806
|
-
confidence: (0,
|
|
7122
|
+
type: import_types24.EdgeType.RUNS_ON,
|
|
7123
|
+
provenance: import_types24.Provenance.EXTRACTED,
|
|
7124
|
+
confidence: (0, import_types24.confidenceForExtracted)("structural"),
|
|
6807
7125
|
evidence: {
|
|
6808
7126
|
file: evidenceFile,
|
|
6809
7127
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -6818,15 +7136,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6818
7136
|
graph.addNode(portNode.id, portNode);
|
|
6819
7137
|
nodesAdded++;
|
|
6820
7138
|
}
|
|
6821
|
-
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id,
|
|
7139
|
+
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
|
|
6822
7140
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
6823
7141
|
const portEdge = {
|
|
6824
7142
|
id: portEdgeId,
|
|
6825
7143
|
source: fileNodeId,
|
|
6826
7144
|
target: portNode.id,
|
|
6827
|
-
type:
|
|
6828
|
-
provenance:
|
|
6829
|
-
confidence: (0,
|
|
7145
|
+
type: import_types24.EdgeType.CONNECTS_TO,
|
|
7146
|
+
provenance: import_types24.Provenance.EXTRACTED,
|
|
7147
|
+
confidence: (0, import_types24.confidenceForExtracted)("structural"),
|
|
6830
7148
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
6831
7149
|
};
|
|
6832
7150
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -6839,8 +7157,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6839
7157
|
// src/extract/infra/terraform.ts
|
|
6840
7158
|
init_cjs_shims();
|
|
6841
7159
|
var import_node_fs17 = require("fs");
|
|
6842
|
-
var
|
|
6843
|
-
var
|
|
7160
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
7161
|
+
var import_types25 = require("@neat.is/types");
|
|
6844
7162
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
6845
7163
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
6846
7164
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
@@ -6850,11 +7168,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
|
|
|
6850
7168
|
for (const entry of entries) {
|
|
6851
7169
|
if (entry.isDirectory()) {
|
|
6852
7170
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
6853
|
-
const child =
|
|
7171
|
+
const child = import_node_path34.default.join(start, entry.name);
|
|
6854
7172
|
if (await isPythonVenvDir(child)) continue;
|
|
6855
7173
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
6856
7174
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
6857
|
-
out.push(
|
|
7175
|
+
out.push(import_node_path34.default.join(start, entry.name));
|
|
6858
7176
|
}
|
|
6859
7177
|
}
|
|
6860
7178
|
return out;
|
|
@@ -6886,7 +7204,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
6886
7204
|
const files = await walkTfFiles(scanPath);
|
|
6887
7205
|
for (const file of files) {
|
|
6888
7206
|
const content = await import_node_fs17.promises.readFile(file, "utf8");
|
|
6889
|
-
const evidenceFile = toPosix2(
|
|
7207
|
+
const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
|
|
6890
7208
|
const resources = [];
|
|
6891
7209
|
const byKey = /* @__PURE__ */ new Map();
|
|
6892
7210
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -6921,16 +7239,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
6921
7239
|
if (!target) continue;
|
|
6922
7240
|
if (seen.has(target.nodeId)) continue;
|
|
6923
7241
|
seen.add(target.nodeId);
|
|
6924
|
-
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
7242
|
+
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
|
|
6925
7243
|
if (graph.hasEdge(edgeId)) continue;
|
|
6926
7244
|
const line = lineAt2(content, resource.bodyOffset + ref.index);
|
|
6927
7245
|
const edge = {
|
|
6928
7246
|
id: edgeId,
|
|
6929
7247
|
source: resource.nodeId,
|
|
6930
7248
|
target: target.nodeId,
|
|
6931
|
-
type:
|
|
6932
|
-
provenance:
|
|
6933
|
-
confidence: (0,
|
|
7249
|
+
type: import_types25.EdgeType.DEPENDS_ON,
|
|
7250
|
+
provenance: import_types25.Provenance.EXTRACTED,
|
|
7251
|
+
confidence: (0, import_types25.confidenceForExtracted)("structural"),
|
|
6934
7252
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
6935
7253
|
};
|
|
6936
7254
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -6944,7 +7262,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
6944
7262
|
// src/extract/infra/k8s.ts
|
|
6945
7263
|
init_cjs_shims();
|
|
6946
7264
|
var import_node_fs18 = require("fs");
|
|
6947
|
-
var
|
|
7265
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
6948
7266
|
var import_yaml3 = require("yaml");
|
|
6949
7267
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
6950
7268
|
Service: "k8s-service",
|
|
@@ -6962,11 +7280,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
|
6962
7280
|
for (const entry of entries) {
|
|
6963
7281
|
if (entry.isDirectory()) {
|
|
6964
7282
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6965
|
-
const child =
|
|
7283
|
+
const child = import_node_path35.default.join(start, entry.name);
|
|
6966
7284
|
if (await isPythonVenvDir(child)) continue;
|
|
6967
7285
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
6968
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
6969
|
-
out.push(
|
|
7286
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry.name))) {
|
|
7287
|
+
out.push(import_node_path35.default.join(start, entry.name));
|
|
6970
7288
|
}
|
|
6971
7289
|
}
|
|
6972
7290
|
return out;
|
|
@@ -7000,13 +7318,13 @@ async function addK8sResources(graph, scanPath) {
|
|
|
7000
7318
|
// src/extract/infra/cloudflare.ts
|
|
7001
7319
|
init_cjs_shims();
|
|
7002
7320
|
var import_node_fs19 = require("fs");
|
|
7003
|
-
var
|
|
7321
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
7004
7322
|
var import_smol_toml2 = require("smol-toml");
|
|
7005
|
-
var
|
|
7323
|
+
var import_types26 = require("@neat.is/types");
|
|
7006
7324
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
7007
7325
|
async function readWranglerConfig(dir) {
|
|
7008
7326
|
for (const filename of WRANGLER_FILENAMES) {
|
|
7009
|
-
const abs =
|
|
7327
|
+
const abs = import_node_path36.default.join(dir, filename);
|
|
7010
7328
|
if (!await exists(abs)) continue;
|
|
7011
7329
|
const raw = await import_node_fs19.promises.readFile(abs, "utf8");
|
|
7012
7330
|
const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
@@ -7050,8 +7368,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
|
|
|
7050
7368
|
source: anchorId,
|
|
7051
7369
|
target: node.id,
|
|
7052
7370
|
type: edgeType,
|
|
7053
|
-
provenance:
|
|
7054
|
-
confidence: (0,
|
|
7371
|
+
provenance: import_types26.Provenance.EXTRACTED,
|
|
7372
|
+
confidence: (0, import_types26.confidenceForExtracted)("structural"),
|
|
7055
7373
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
7056
7374
|
};
|
|
7057
7375
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -7069,11 +7387,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7069
7387
|
try {
|
|
7070
7388
|
read = await readWranglerConfig(service.dir);
|
|
7071
7389
|
} catch (err) {
|
|
7072
|
-
recordExtractionError("infra cloudflare",
|
|
7390
|
+
recordExtractionError("infra cloudflare", import_node_path36.default.relative(scanPath, service.dir), err);
|
|
7073
7391
|
continue;
|
|
7074
7392
|
}
|
|
7075
7393
|
if (!read || !read.config.name) continue;
|
|
7076
|
-
const evidenceFile = toPosix2(
|
|
7394
|
+
const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, read.relFile)));
|
|
7077
7395
|
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
7078
7396
|
}
|
|
7079
7397
|
for (const worker of discovered) {
|
|
@@ -7085,7 +7403,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7085
7403
|
}
|
|
7086
7404
|
let anchorId = service.node.id;
|
|
7087
7405
|
if (config.main) {
|
|
7088
|
-
const entryRelPath = toPosix2(
|
|
7406
|
+
const entryRelPath = toPosix2(import_node_path36.default.normalize(config.main));
|
|
7089
7407
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
7090
7408
|
graph,
|
|
7091
7409
|
service.pkg.name,
|
|
@@ -7112,15 +7430,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7112
7430
|
nodesAdded++;
|
|
7113
7431
|
}
|
|
7114
7432
|
if (runtimeNode.id !== anchorId) {
|
|
7115
|
-
const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id,
|
|
7433
|
+
const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
|
|
7116
7434
|
if (!graph.hasEdge(runsOnId)) {
|
|
7117
7435
|
const edge = {
|
|
7118
7436
|
id: runsOnId,
|
|
7119
7437
|
source: anchorId,
|
|
7120
7438
|
target: runtimeNode.id,
|
|
7121
|
-
type:
|
|
7122
|
-
provenance:
|
|
7123
|
-
confidence: (0,
|
|
7439
|
+
type: import_types26.EdgeType.RUNS_ON,
|
|
7440
|
+
provenance: import_types26.Provenance.EXTRACTED,
|
|
7441
|
+
confidence: (0, import_types26.confidenceForExtracted)("structural"),
|
|
7124
7442
|
evidence: {
|
|
7125
7443
|
file: evidenceFile,
|
|
7126
7444
|
...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
|
|
@@ -7134,7 +7452,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7134
7452
|
const result = addResourceEdge(
|
|
7135
7453
|
graph,
|
|
7136
7454
|
anchorId,
|
|
7137
|
-
|
|
7455
|
+
import_types26.EdgeType.CONNECTS_TO,
|
|
7138
7456
|
"cloudflare-route",
|
|
7139
7457
|
route,
|
|
7140
7458
|
evidenceFile,
|
|
@@ -7158,7 +7476,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7158
7476
|
const result = addResourceEdge(
|
|
7159
7477
|
graph,
|
|
7160
7478
|
anchorId,
|
|
7161
|
-
|
|
7479
|
+
import_types26.EdgeType.DEPENDS_ON,
|
|
7162
7480
|
group.kind,
|
|
7163
7481
|
name,
|
|
7164
7482
|
evidenceFile,
|
|
@@ -7172,7 +7490,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7172
7490
|
const result = addResourceEdge(
|
|
7173
7491
|
graph,
|
|
7174
7492
|
anchorId,
|
|
7175
|
-
|
|
7493
|
+
import_types26.EdgeType.DEPENDS_ON,
|
|
7176
7494
|
"cloudflare-cron",
|
|
7177
7495
|
cron,
|
|
7178
7496
|
evidenceFile,
|
|
@@ -7185,7 +7503,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7185
7503
|
const result = addResourceEdge(
|
|
7186
7504
|
graph,
|
|
7187
7505
|
anchorId,
|
|
7188
|
-
|
|
7506
|
+
import_types26.EdgeType.DEPENDS_ON,
|
|
7189
7507
|
"cloudflare-env-var",
|
|
7190
7508
|
varName,
|
|
7191
7509
|
evidenceFile,
|
|
@@ -7198,15 +7516,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7198
7516
|
if (!svc.service) continue;
|
|
7199
7517
|
const target = workerIndex.get(svc.service);
|
|
7200
7518
|
if (target && target.anchorId !== anchorId) {
|
|
7201
|
-
const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId,
|
|
7519
|
+
const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
|
|
7202
7520
|
if (!graph.hasEdge(edgeId)) {
|
|
7203
7521
|
const edge = {
|
|
7204
7522
|
id: edgeId,
|
|
7205
7523
|
source: anchorId,
|
|
7206
7524
|
target: target.anchorId,
|
|
7207
|
-
type:
|
|
7208
|
-
provenance:
|
|
7209
|
-
confidence: (0,
|
|
7525
|
+
type: import_types26.EdgeType.CALLS,
|
|
7526
|
+
provenance: import_types26.Provenance.EXTRACTED,
|
|
7527
|
+
confidence: (0, import_types26.confidenceForExtracted)("structural"),
|
|
7210
7528
|
evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
|
|
7211
7529
|
};
|
|
7212
7530
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -7217,7 +7535,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7217
7535
|
const result = addResourceEdge(
|
|
7218
7536
|
graph,
|
|
7219
7537
|
anchorId,
|
|
7220
|
-
|
|
7538
|
+
import_types26.EdgeType.DEPENDS_ON,
|
|
7221
7539
|
"cloudflare-service-binding",
|
|
7222
7540
|
svc.service,
|
|
7223
7541
|
evidenceFile,
|
|
@@ -7233,12 +7551,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7233
7551
|
// src/extract/infra/vercel.ts
|
|
7234
7552
|
init_cjs_shims();
|
|
7235
7553
|
var import_node_fs20 = require("fs");
|
|
7236
|
-
var
|
|
7237
|
-
var
|
|
7554
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
7555
|
+
var import_types27 = require("@neat.is/types");
|
|
7238
7556
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
7239
7557
|
async function readVercelConfig(dir) {
|
|
7240
7558
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
7241
|
-
const abs =
|
|
7559
|
+
const abs = import_node_path37.default.join(dir, filename);
|
|
7242
7560
|
if (!await exists(abs)) continue;
|
|
7243
7561
|
const raw = await import_node_fs20.promises.readFile(abs, "utf8");
|
|
7244
7562
|
const config = JSON.parse(maskCommentsInSource(raw));
|
|
@@ -7247,7 +7565,7 @@ async function readVercelConfig(dir) {
|
|
|
7247
7565
|
return null;
|
|
7248
7566
|
}
|
|
7249
7567
|
async function readLinkedProjectName(dir) {
|
|
7250
|
-
const abs =
|
|
7568
|
+
const abs = import_node_path37.default.join(dir, ".vercel", "project.json");
|
|
7251
7569
|
if (!await exists(abs)) return void 0;
|
|
7252
7570
|
const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
|
|
7253
7571
|
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
@@ -7265,7 +7583,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
7265
7583
|
read = await readVercelConfig(service.dir);
|
|
7266
7584
|
projectName = await readLinkedProjectName(service.dir);
|
|
7267
7585
|
} catch (err) {
|
|
7268
|
-
recordExtractionError("infra vercel",
|
|
7586
|
+
recordExtractionError("infra vercel", import_node_path37.default.relative(scanPath, service.dir), err);
|
|
7269
7587
|
continue;
|
|
7270
7588
|
}
|
|
7271
7589
|
if (!read && !projectName) continue;
|
|
@@ -7281,7 +7599,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
7281
7599
|
const anchorId = service.node.id;
|
|
7282
7600
|
if (!read) continue;
|
|
7283
7601
|
const { config, relFile, raw } = read;
|
|
7284
|
-
const evidenceFile = toPosix2(
|
|
7602
|
+
const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
|
|
7285
7603
|
const add = (edgeType, kind, name) => {
|
|
7286
7604
|
if (!name) return;
|
|
7287
7605
|
const result = emitPlatformResourceEdge(
|
|
@@ -7297,12 +7615,12 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
7297
7615
|
nodesAdded += result.nodesAdded;
|
|
7298
7616
|
edgesAdded += result.edgesAdded;
|
|
7299
7617
|
};
|
|
7300
|
-
add(
|
|
7301
|
-
for (const cron of config.crons ?? []) add(
|
|
7302
|
-
for (const varName of Object.keys(config.env ?? {})) add(
|
|
7303
|
-
for (const varName of Object.keys(config.build?.env ?? {})) add(
|
|
7618
|
+
add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
|
|
7619
|
+
for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
|
|
7620
|
+
for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
7621
|
+
for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
7304
7622
|
for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
|
|
7305
|
-
add(
|
|
7623
|
+
add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
|
|
7306
7624
|
}
|
|
7307
7625
|
}
|
|
7308
7626
|
return { nodesAdded, edgesAdded };
|
|
@@ -7311,13 +7629,13 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
7311
7629
|
// src/extract/infra/railway.ts
|
|
7312
7630
|
init_cjs_shims();
|
|
7313
7631
|
var import_node_fs21 = require("fs");
|
|
7314
|
-
var
|
|
7632
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
7315
7633
|
var import_smol_toml3 = require("smol-toml");
|
|
7316
|
-
var
|
|
7634
|
+
var import_types28 = require("@neat.is/types");
|
|
7317
7635
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
7318
7636
|
async function readRailwayConfig(dir) {
|
|
7319
7637
|
for (const filename of RAILWAY_FILENAMES) {
|
|
7320
|
-
const abs =
|
|
7638
|
+
const abs = import_node_path38.default.join(dir, filename);
|
|
7321
7639
|
if (!await exists(abs)) continue;
|
|
7322
7640
|
const raw = await import_node_fs21.promises.readFile(abs, "utf8");
|
|
7323
7641
|
const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
@@ -7333,7 +7651,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
7333
7651
|
try {
|
|
7334
7652
|
read = await readRailwayConfig(service.dir);
|
|
7335
7653
|
} catch (err) {
|
|
7336
|
-
recordExtractionError("infra railway",
|
|
7654
|
+
recordExtractionError("infra railway", import_node_path38.default.relative(scanPath, service.dir), err);
|
|
7337
7655
|
continue;
|
|
7338
7656
|
}
|
|
7339
7657
|
if (!read) continue;
|
|
@@ -7343,7 +7661,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
7343
7661
|
}
|
|
7344
7662
|
const anchorId = service.node.id;
|
|
7345
7663
|
const { config, relFile, raw } = read;
|
|
7346
|
-
const evidenceFile = toPosix2(
|
|
7664
|
+
const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
|
|
7347
7665
|
const add = (edgeType, kind, name) => {
|
|
7348
7666
|
if (!name) return;
|
|
7349
7667
|
const result = emitPlatformResourceEdge(
|
|
@@ -7359,9 +7677,9 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
7359
7677
|
nodesAdded += result.nodesAdded;
|
|
7360
7678
|
edgesAdded += result.edgesAdded;
|
|
7361
7679
|
};
|
|
7362
|
-
add(
|
|
7363
|
-
add(
|
|
7364
|
-
add(
|
|
7680
|
+
add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
|
|
7681
|
+
add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
|
|
7682
|
+
add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
|
|
7365
7683
|
}
|
|
7366
7684
|
return { nodesAdded, edgesAdded };
|
|
7367
7685
|
}
|
|
@@ -7369,12 +7687,12 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
7369
7687
|
// src/extract/infra/supabase.ts
|
|
7370
7688
|
init_cjs_shims();
|
|
7371
7689
|
var import_node_fs22 = require("fs");
|
|
7372
|
-
var
|
|
7690
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
7373
7691
|
var import_smol_toml4 = require("smol-toml");
|
|
7374
|
-
var
|
|
7692
|
+
var import_types29 = require("@neat.is/types");
|
|
7375
7693
|
async function readSupabaseConfig(dir) {
|
|
7376
|
-
const relFile =
|
|
7377
|
-
const abs =
|
|
7694
|
+
const relFile = import_node_path39.default.join("supabase", "config.toml");
|
|
7695
|
+
const abs = import_node_path39.default.join(dir, relFile);
|
|
7378
7696
|
if (!await exists(abs)) return null;
|
|
7379
7697
|
const raw = await import_node_fs22.promises.readFile(abs, "utf8");
|
|
7380
7698
|
const config = (0, import_smol_toml4.parse)(raw);
|
|
@@ -7388,7 +7706,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
7388
7706
|
try {
|
|
7389
7707
|
read = await readSupabaseConfig(service.dir);
|
|
7390
7708
|
} catch (err) {
|
|
7391
|
-
recordExtractionError("infra supabase",
|
|
7709
|
+
recordExtractionError("infra supabase", import_node_path39.default.relative(scanPath, service.dir), err);
|
|
7392
7710
|
continue;
|
|
7393
7711
|
}
|
|
7394
7712
|
if (!read) continue;
|
|
@@ -7403,7 +7721,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
7403
7721
|
});
|
|
7404
7722
|
}
|
|
7405
7723
|
const anchorId = service.node.id;
|
|
7406
|
-
const evidenceFile = toPosix2(
|
|
7724
|
+
const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
|
|
7407
7725
|
const add = (edgeType, kind, name) => {
|
|
7408
7726
|
if (!name) return;
|
|
7409
7727
|
const result = emitPlatformResourceEdge(
|
|
@@ -7419,10 +7737,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
7419
7737
|
nodesAdded += result.nodesAdded;
|
|
7420
7738
|
edgesAdded += result.edgesAdded;
|
|
7421
7739
|
};
|
|
7422
|
-
add(
|
|
7423
|
-
for (const fn of Object.keys(config.functions ?? {})) add(
|
|
7424
|
-
if (config.storage) add(
|
|
7425
|
-
if (config.auth) add(
|
|
7740
|
+
add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
|
|
7741
|
+
for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
|
|
7742
|
+
if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
|
|
7743
|
+
if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
|
|
7426
7744
|
}
|
|
7427
7745
|
return { nodesAdded, edgesAdded };
|
|
7428
7746
|
}
|
|
@@ -7444,17 +7762,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
7444
7762
|
}
|
|
7445
7763
|
|
|
7446
7764
|
// src/extract/index.ts
|
|
7447
|
-
var
|
|
7765
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
7448
7766
|
|
|
7449
7767
|
// src/extract/retire.ts
|
|
7450
7768
|
init_cjs_shims();
|
|
7451
7769
|
var import_node_fs23 = require("fs");
|
|
7452
|
-
var
|
|
7453
|
-
var
|
|
7770
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
7771
|
+
var import_types30 = require("@neat.is/types");
|
|
7454
7772
|
function dropOrphanedFileNodes(graph) {
|
|
7455
7773
|
const orphans = [];
|
|
7456
7774
|
graph.forEachNode((id, attrs) => {
|
|
7457
|
-
if (attrs.type !==
|
|
7775
|
+
if (attrs.type !== import_types30.NodeType.FileNode) return;
|
|
7458
7776
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
7459
7777
|
orphans.push(id);
|
|
7460
7778
|
}
|
|
@@ -7467,14 +7785,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
7467
7785
|
const bases = [scanPath, ...serviceDirs];
|
|
7468
7786
|
graph.forEachEdge((id, attrs) => {
|
|
7469
7787
|
const edge = attrs;
|
|
7470
|
-
if (edge.provenance !==
|
|
7788
|
+
if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
|
|
7471
7789
|
const evidenceFile = edge.evidence?.file;
|
|
7472
7790
|
if (!evidenceFile) return;
|
|
7473
|
-
if (
|
|
7791
|
+
if (import_node_path40.default.isAbsolute(evidenceFile)) {
|
|
7474
7792
|
if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
|
|
7475
7793
|
return;
|
|
7476
7794
|
}
|
|
7477
|
-
const found = bases.some((base) => (0, import_node_fs23.existsSync)(
|
|
7795
|
+
const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path40.default.join(base, evidenceFile)));
|
|
7478
7796
|
if (!found) toDrop.push(id);
|
|
7479
7797
|
});
|
|
7480
7798
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -7516,7 +7834,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7516
7834
|
}
|
|
7517
7835
|
const droppedEntries = drainDroppedExtracted();
|
|
7518
7836
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
7519
|
-
const rejectedPath =
|
|
7837
|
+
const rejectedPath = import_node_path41.default.join(import_node_path41.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
7520
7838
|
try {
|
|
7521
7839
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
7522
7840
|
} catch (err) {
|
|
@@ -7551,8 +7869,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7551
7869
|
// src/persist.ts
|
|
7552
7870
|
init_cjs_shims();
|
|
7553
7871
|
var import_node_fs24 = require("fs");
|
|
7554
|
-
var
|
|
7555
|
-
var
|
|
7872
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
7873
|
+
var import_types31 = require("@neat.is/types");
|
|
7556
7874
|
var SCHEMA_VERSION = 4;
|
|
7557
7875
|
function migrateV1ToV2(payload) {
|
|
7558
7876
|
const nodes = payload.graph.nodes;
|
|
@@ -7574,12 +7892,12 @@ function migrateV2ToV3(payload) {
|
|
|
7574
7892
|
for (const edge of edges) {
|
|
7575
7893
|
const attrs = edge.attributes;
|
|
7576
7894
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
7577
|
-
attrs.provenance =
|
|
7895
|
+
attrs.provenance = import_types31.Provenance.OBSERVED;
|
|
7578
7896
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
7579
7897
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
7580
7898
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
7581
7899
|
if (type && source && target) {
|
|
7582
|
-
const newId = (0,
|
|
7900
|
+
const newId = (0, import_types31.observedEdgeId)(source, target, type);
|
|
7583
7901
|
attrs.id = newId;
|
|
7584
7902
|
if (edge.key) edge.key = newId;
|
|
7585
7903
|
}
|
|
@@ -7588,7 +7906,7 @@ function migrateV2ToV3(payload) {
|
|
|
7588
7906
|
return { ...payload, schemaVersion: 3 };
|
|
7589
7907
|
}
|
|
7590
7908
|
async function ensureDir(filePath) {
|
|
7591
|
-
await import_node_fs24.promises.mkdir(
|
|
7909
|
+
await import_node_fs24.promises.mkdir(import_node_path42.default.dirname(filePath), { recursive: true });
|
|
7592
7910
|
}
|
|
7593
7911
|
async function saveGraphToDisk(graph, outPath) {
|
|
7594
7912
|
await ensureDir(outPath);
|
|
@@ -7671,19 +7989,19 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
7671
7989
|
init_cjs_shims();
|
|
7672
7990
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
7673
7991
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
7674
|
-
var
|
|
7992
|
+
var import_types34 = require("@neat.is/types");
|
|
7675
7993
|
|
|
7676
7994
|
// src/extend/index.ts
|
|
7677
7995
|
init_cjs_shims();
|
|
7678
7996
|
var import_node_fs26 = require("fs");
|
|
7679
|
-
var
|
|
7997
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
7680
7998
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
7681
7999
|
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
7682
8000
|
|
|
7683
8001
|
// src/installers/package-manager.ts
|
|
7684
8002
|
init_cjs_shims();
|
|
7685
8003
|
var import_node_fs25 = require("fs");
|
|
7686
|
-
var
|
|
8004
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
7687
8005
|
var import_node_child_process = require("child_process");
|
|
7688
8006
|
var LOCKFILE_PRIORITY = [
|
|
7689
8007
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -7705,22 +8023,22 @@ async function exists2(p) {
|
|
|
7705
8023
|
}
|
|
7706
8024
|
}
|
|
7707
8025
|
async function detectPackageManager(serviceDir) {
|
|
7708
|
-
let dir =
|
|
8026
|
+
let dir = import_node_path43.default.resolve(serviceDir);
|
|
7709
8027
|
const stops = /* @__PURE__ */ new Set();
|
|
7710
8028
|
for (let i = 0; i < 64; i++) {
|
|
7711
8029
|
if (stops.has(dir)) break;
|
|
7712
8030
|
stops.add(dir);
|
|
7713
8031
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
7714
|
-
const lockPath =
|
|
8032
|
+
const lockPath = import_node_path43.default.join(dir, candidate.lockfile);
|
|
7715
8033
|
if (await exists2(lockPath)) {
|
|
7716
8034
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
7717
8035
|
}
|
|
7718
8036
|
}
|
|
7719
|
-
const parent =
|
|
8037
|
+
const parent = import_node_path43.default.dirname(dir);
|
|
7720
8038
|
if (parent === dir) break;
|
|
7721
8039
|
dir = parent;
|
|
7722
8040
|
}
|
|
7723
|
-
return { pm: "npm", cwd:
|
|
8041
|
+
return { pm: "npm", cwd: import_node_path43.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
7724
8042
|
}
|
|
7725
8043
|
async function runPackageManagerInstall(cmd) {
|
|
7726
8044
|
return new Promise((resolve) => {
|
|
@@ -7769,7 +8087,7 @@ async function fileExists2(p) {
|
|
|
7769
8087
|
}
|
|
7770
8088
|
}
|
|
7771
8089
|
async function readPackageJson(scanPath) {
|
|
7772
|
-
const pkgPath =
|
|
8090
|
+
const pkgPath = import_node_path44.default.join(scanPath, "package.json");
|
|
7773
8091
|
const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
|
|
7774
8092
|
return JSON.parse(raw);
|
|
7775
8093
|
}
|
|
@@ -7788,11 +8106,11 @@ async function findHookFiles(scanPath) {
|
|
|
7788
8106
|
for (const entry of entries) {
|
|
7789
8107
|
if (entry.isDirectory()) {
|
|
7790
8108
|
if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
|
|
7791
|
-
await walk3(
|
|
8109
|
+
await walk3(import_node_path44.default.join(dir, entry.name));
|
|
7792
8110
|
} else if (entry.isFile()) {
|
|
7793
8111
|
if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
|
|
7794
|
-
const rel =
|
|
7795
|
-
found.push(rel.split(
|
|
8112
|
+
const rel = import_node_path44.default.relative(scanPath, import_node_path44.default.join(dir, entry.name));
|
|
8113
|
+
found.push(rel.split(import_node_path44.default.sep).join("/"));
|
|
7796
8114
|
}
|
|
7797
8115
|
}
|
|
7798
8116
|
}
|
|
@@ -7803,7 +8121,7 @@ async function findHookFiles(scanPath) {
|
|
|
7803
8121
|
async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
7804
8122
|
let fallback = null;
|
|
7805
8123
|
for (const file of hookFiles) {
|
|
7806
|
-
const content = await import_node_fs26.promises.readFile(
|
|
8124
|
+
const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(scanPath, file), "utf8");
|
|
7807
8125
|
const patched = splicedContent(content, snippet2);
|
|
7808
8126
|
if (patched !== null) return { file, content, patched };
|
|
7809
8127
|
if (fallback === null) fallback = { file, content };
|
|
@@ -7811,11 +8129,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
|
7811
8129
|
return { file: fallback.file, content: fallback.content, patched: null };
|
|
7812
8130
|
}
|
|
7813
8131
|
function extendLogPath() {
|
|
7814
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
8132
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path44.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
7815
8133
|
}
|
|
7816
8134
|
async function appendExtendLog(entry) {
|
|
7817
8135
|
const logPath = extendLogPath();
|
|
7818
|
-
await import_node_fs26.promises.mkdir(
|
|
8136
|
+
await import_node_fs26.promises.mkdir(import_node_path44.default.dirname(logPath), { recursive: true });
|
|
7819
8137
|
await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
7820
8138
|
}
|
|
7821
8139
|
function splicedContent(fileContent, snippet2) {
|
|
@@ -7874,7 +8192,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
7874
8192
|
}
|
|
7875
8193
|
async function describeProjectInstrumentation(ctx) {
|
|
7876
8194
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7877
|
-
const envNeat = await fileExists2(
|
|
8195
|
+
const envNeat = await fileExists2(import_node_path44.default.join(ctx.scanPath, ".env.neat"));
|
|
7878
8196
|
const registryInstrPackages = new Set(
|
|
7879
8197
|
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
7880
8198
|
);
|
|
@@ -7896,7 +8214,7 @@ async function applyExtension(ctx, args, options) {
|
|
|
7896
8214
|
);
|
|
7897
8215
|
}
|
|
7898
8216
|
for (const file of hookFiles) {
|
|
7899
|
-
const content = await import_node_fs26.promises.readFile(
|
|
8217
|
+
const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
|
|
7900
8218
|
if (content.includes(args.registration_snippet)) {
|
|
7901
8219
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
7902
8220
|
}
|
|
@@ -7908,10 +8226,10 @@ async function applyExtension(ctx, args, options) {
|
|
|
7908
8226
|
);
|
|
7909
8227
|
}
|
|
7910
8228
|
const primaryFile = primary.file;
|
|
7911
|
-
const primaryPath =
|
|
8229
|
+
const primaryPath = import_node_path44.default.join(ctx.scanPath, primaryFile);
|
|
7912
8230
|
const filesTouched = [];
|
|
7913
8231
|
const depsAdded = [];
|
|
7914
|
-
const pkgPath =
|
|
8232
|
+
const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
|
|
7915
8233
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7916
8234
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
7917
8235
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
@@ -7950,7 +8268,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7950
8268
|
};
|
|
7951
8269
|
}
|
|
7952
8270
|
for (const file of hookFiles) {
|
|
7953
|
-
const content = await import_node_fs26.promises.readFile(
|
|
8271
|
+
const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
|
|
7954
8272
|
if (content.includes(args.registration_snippet)) {
|
|
7955
8273
|
return {
|
|
7956
8274
|
library: args.library,
|
|
@@ -7991,7 +8309,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
7991
8309
|
if (!match) {
|
|
7992
8310
|
return { undone: false, message: "no apply found for library" };
|
|
7993
8311
|
}
|
|
7994
|
-
const pkgPath =
|
|
8312
|
+
const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
|
|
7995
8313
|
if (await fileExists2(pkgPath)) {
|
|
7996
8314
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7997
8315
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
@@ -8002,7 +8320,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
8002
8320
|
}
|
|
8003
8321
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
8004
8322
|
for (const file of hookFiles) {
|
|
8005
|
-
const filePath =
|
|
8323
|
+
const filePath = import_node_path44.default.join(ctx.scanPath, file);
|
|
8006
8324
|
const content = await import_node_fs26.promises.readFile(filePath, "utf8");
|
|
8007
8325
|
if (content.includes(match.registration_snippet)) {
|
|
8008
8326
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
@@ -8018,39 +8336,39 @@ async function rollbackExtension(ctx, args) {
|
|
|
8018
8336
|
|
|
8019
8337
|
// src/divergences.ts
|
|
8020
8338
|
init_cjs_shims();
|
|
8021
|
-
var
|
|
8339
|
+
var import_types32 = require("@neat.is/types");
|
|
8022
8340
|
function bucketKey(source, target, type) {
|
|
8023
8341
|
return `${type}|${source}|${target}`;
|
|
8024
8342
|
}
|
|
8025
8343
|
function bucketSourceFor(graph, edge) {
|
|
8026
|
-
if (edge.type !==
|
|
8027
|
-
const parsed = (0,
|
|
8344
|
+
if (edge.type !== import_types32.EdgeType.CONNECTS_TO) return edge.source;
|
|
8345
|
+
const parsed = (0, import_types32.parseFileId)(edge.source);
|
|
8028
8346
|
if (!parsed || !graph.hasNode(edge.target)) return edge.source;
|
|
8029
8347
|
const target = graph.getNodeAttributes(edge.target);
|
|
8030
|
-
if (target.type !==
|
|
8031
|
-
return (0,
|
|
8348
|
+
if (target.type !== import_types32.NodeType.DatabaseNode) return edge.source;
|
|
8349
|
+
return (0, import_types32.serviceId)(parsed.service);
|
|
8032
8350
|
}
|
|
8033
8351
|
function bucketEdges(graph) {
|
|
8034
8352
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
8035
8353
|
graph.forEachEdge((id, attrs) => {
|
|
8036
8354
|
const e = attrs;
|
|
8037
|
-
const parsed = (0,
|
|
8355
|
+
const parsed = (0, import_types32.parseEdgeId)(id);
|
|
8038
8356
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
8039
8357
|
const source = bucketSourceFor(graph, e);
|
|
8040
8358
|
const key = bucketKey(source, e.target, e.type);
|
|
8041
8359
|
const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
|
|
8042
8360
|
switch (provenance) {
|
|
8043
|
-
case
|
|
8361
|
+
case import_types32.Provenance.EXTRACTED:
|
|
8044
8362
|
cur.extracted = e;
|
|
8045
8363
|
break;
|
|
8046
|
-
case
|
|
8364
|
+
case import_types32.Provenance.OBSERVED:
|
|
8047
8365
|
cur.observed = e;
|
|
8048
8366
|
break;
|
|
8049
|
-
case
|
|
8367
|
+
case import_types32.Provenance.INFERRED:
|
|
8050
8368
|
cur.inferred = e;
|
|
8051
8369
|
break;
|
|
8052
8370
|
default:
|
|
8053
|
-
if (e.provenance ===
|
|
8371
|
+
if (e.provenance === import_types32.Provenance.STALE) cur.stale = e;
|
|
8054
8372
|
}
|
|
8055
8373
|
buckets2.set(key, cur);
|
|
8056
8374
|
});
|
|
@@ -8059,12 +8377,12 @@ function bucketEdges(graph) {
|
|
|
8059
8377
|
function nodeIsFrontier(graph, nodeId) {
|
|
8060
8378
|
if (!graph.hasNode(nodeId)) return false;
|
|
8061
8379
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
8062
|
-
return attrs.type ===
|
|
8380
|
+
return attrs.type === import_types32.NodeType.FrontierNode;
|
|
8063
8381
|
}
|
|
8064
8382
|
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
8065
8383
|
if (!graph.hasNode(nodeId)) return false;
|
|
8066
8384
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
8067
|
-
return attrs.type ===
|
|
8385
|
+
return attrs.type === import_types32.NodeType.WebSocketChannelNode;
|
|
8068
8386
|
}
|
|
8069
8387
|
function clampConfidence(n) {
|
|
8070
8388
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -8084,14 +8402,14 @@ function gradedConfidence(edge) {
|
|
|
8084
8402
|
return clampConfidence(confidenceForEdge(edge));
|
|
8085
8403
|
}
|
|
8086
8404
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8405
|
+
import_types32.EdgeType.CALLS,
|
|
8406
|
+
import_types32.EdgeType.CONNECTS_TO,
|
|
8407
|
+
import_types32.EdgeType.PUBLISHES_TO,
|
|
8408
|
+
import_types32.EdgeType.CONSUMES_FROM
|
|
8091
8409
|
]);
|
|
8092
8410
|
function detectMissingDivergences(graph, bucket) {
|
|
8093
8411
|
const out = [];
|
|
8094
|
-
if (bucket.type ===
|
|
8412
|
+
if (bucket.type === import_types32.EdgeType.CONTAINS) return out;
|
|
8095
8413
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
8096
8414
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
8097
8415
|
out.push({
|
|
@@ -8132,7 +8450,7 @@ function declaredHostFor(svc) {
|
|
|
8132
8450
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
8133
8451
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
8134
8452
|
const e = graph.getEdgeAttributes(edgeId);
|
|
8135
|
-
if (e.type ===
|
|
8453
|
+
if (e.type === import_types32.EdgeType.CONFIGURED_BY && e.provenance === import_types32.Provenance.EXTRACTED) {
|
|
8136
8454
|
return true;
|
|
8137
8455
|
}
|
|
8138
8456
|
}
|
|
@@ -8145,10 +8463,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
8145
8463
|
const out = [];
|
|
8146
8464
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
8147
8465
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
8148
|
-
if (edge.type !==
|
|
8149
|
-
if (edge.provenance !==
|
|
8466
|
+
if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
|
|
8467
|
+
if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
|
|
8150
8468
|
const target = graph.getNodeAttributes(edge.target);
|
|
8151
|
-
if (target.type !==
|
|
8469
|
+
if (target.type !== import_types32.NodeType.DatabaseNode) continue;
|
|
8152
8470
|
const observedHost = target.host?.trim();
|
|
8153
8471
|
if (!observedHost) continue;
|
|
8154
8472
|
if (observedHost === declaredHost) continue;
|
|
@@ -8170,10 +8488,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
8170
8488
|
const deps = svc.dependencies ?? {};
|
|
8171
8489
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
8172
8490
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
8173
|
-
if (edge.type !==
|
|
8174
|
-
if (edge.provenance !==
|
|
8491
|
+
if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
|
|
8492
|
+
if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
|
|
8175
8493
|
const target = graph.getNodeAttributes(edge.target);
|
|
8176
|
-
if (target.type !==
|
|
8494
|
+
if (target.type !== import_types32.NodeType.DatabaseNode) continue;
|
|
8177
8495
|
for (const pair of compatPairs()) {
|
|
8178
8496
|
if (pair.engine !== target.engine) continue;
|
|
8179
8497
|
const declared = deps[pair.driver];
|
|
@@ -8232,7 +8550,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
8232
8550
|
for (const d of all) {
|
|
8233
8551
|
if (d.type !== "host-mismatch") continue;
|
|
8234
8552
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
8235
|
-
declaredHalf.add((0,
|
|
8553
|
+
declaredHalf.add((0, import_types32.databaseId)(d.extractedHost));
|
|
8236
8554
|
}
|
|
8237
8555
|
if (observedHalf.size === 0) return all;
|
|
8238
8556
|
return all.filter((d) => {
|
|
@@ -8251,7 +8569,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
8251
8569
|
}
|
|
8252
8570
|
graph.forEachNode((nodeId, attrs) => {
|
|
8253
8571
|
const n = attrs;
|
|
8254
|
-
if (n.type !==
|
|
8572
|
+
if (n.type !== import_types32.NodeType.ServiceNode) return;
|
|
8255
8573
|
const svc = n;
|
|
8256
8574
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
8257
8575
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -8285,7 +8603,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
8285
8603
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
8286
8604
|
return a.target.localeCompare(b.target);
|
|
8287
8605
|
});
|
|
8288
|
-
return
|
|
8606
|
+
return import_types32.DivergenceResultSchema.parse({
|
|
8289
8607
|
divergences: filtered,
|
|
8290
8608
|
totalAffected: filtered.length,
|
|
8291
8609
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -8416,23 +8734,23 @@ function canonicalJson(value) {
|
|
|
8416
8734
|
|
|
8417
8735
|
// src/projects.ts
|
|
8418
8736
|
init_cjs_shims();
|
|
8419
|
-
var
|
|
8737
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
8420
8738
|
function pathsForProject(project, baseDir) {
|
|
8421
8739
|
if (project === DEFAULT_PROJECT) {
|
|
8422
8740
|
return {
|
|
8423
|
-
snapshotPath:
|
|
8424
|
-
errorsPath:
|
|
8425
|
-
staleEventsPath:
|
|
8426
|
-
embeddingsCachePath:
|
|
8427
|
-
policyViolationsPath:
|
|
8741
|
+
snapshotPath: import_node_path45.default.join(baseDir, "graph.json"),
|
|
8742
|
+
errorsPath: import_node_path45.default.join(baseDir, "errors.ndjson"),
|
|
8743
|
+
staleEventsPath: import_node_path45.default.join(baseDir, "stale-events.ndjson"),
|
|
8744
|
+
embeddingsCachePath: import_node_path45.default.join(baseDir, "embeddings.json"),
|
|
8745
|
+
policyViolationsPath: import_node_path45.default.join(baseDir, "policy-violations.ndjson")
|
|
8428
8746
|
};
|
|
8429
8747
|
}
|
|
8430
8748
|
return {
|
|
8431
|
-
snapshotPath:
|
|
8432
|
-
errorsPath:
|
|
8433
|
-
staleEventsPath:
|
|
8434
|
-
embeddingsCachePath:
|
|
8435
|
-
policyViolationsPath:
|
|
8749
|
+
snapshotPath: import_node_path45.default.join(baseDir, `${project}.json`),
|
|
8750
|
+
errorsPath: import_node_path45.default.join(baseDir, `errors.${project}.ndjson`),
|
|
8751
|
+
staleEventsPath: import_node_path45.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
8752
|
+
embeddingsCachePath: import_node_path45.default.join(baseDir, `embeddings.${project}.json`),
|
|
8753
|
+
policyViolationsPath: import_node_path45.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
8436
8754
|
};
|
|
8437
8755
|
}
|
|
8438
8756
|
var Projects = class {
|
|
@@ -8470,23 +8788,23 @@ var Projects = class {
|
|
|
8470
8788
|
init_cjs_shims();
|
|
8471
8789
|
var import_node_fs28 = require("fs");
|
|
8472
8790
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
8473
|
-
var
|
|
8474
|
-
var
|
|
8791
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
8792
|
+
var import_types33 = require("@neat.is/types");
|
|
8475
8793
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
8476
8794
|
var LOCK_RETRY_MS = 50;
|
|
8477
8795
|
function neatHome() {
|
|
8478
8796
|
const override = process.env.NEAT_HOME;
|
|
8479
|
-
if (override && override.length > 0) return
|
|
8480
|
-
return
|
|
8797
|
+
if (override && override.length > 0) return import_node_path46.default.resolve(override);
|
|
8798
|
+
return import_node_path46.default.join(import_node_os3.default.homedir(), ".neat");
|
|
8481
8799
|
}
|
|
8482
8800
|
function registryPath() {
|
|
8483
|
-
return
|
|
8801
|
+
return import_node_path46.default.join(neatHome(), "projects.json");
|
|
8484
8802
|
}
|
|
8485
8803
|
function registryLockPath() {
|
|
8486
|
-
return
|
|
8804
|
+
return import_node_path46.default.join(neatHome(), "projects.json.lock");
|
|
8487
8805
|
}
|
|
8488
8806
|
function daemonPidPath() {
|
|
8489
|
-
return
|
|
8807
|
+
return import_node_path46.default.join(neatHome(), "neatd.pid");
|
|
8490
8808
|
}
|
|
8491
8809
|
function isPidAliveDefault(pid) {
|
|
8492
8810
|
try {
|
|
@@ -8546,7 +8864,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
8546
8864
|
}
|
|
8547
8865
|
}
|
|
8548
8866
|
async function normalizeProjectPath(input) {
|
|
8549
|
-
const resolved =
|
|
8867
|
+
const resolved = import_node_path46.default.resolve(input);
|
|
8550
8868
|
try {
|
|
8551
8869
|
return await import_node_fs28.promises.realpath(resolved);
|
|
8552
8870
|
} catch {
|
|
@@ -8554,7 +8872,7 @@ async function normalizeProjectPath(input) {
|
|
|
8554
8872
|
}
|
|
8555
8873
|
}
|
|
8556
8874
|
async function writeAtomically(target, contents) {
|
|
8557
|
-
await import_node_fs28.promises.mkdir(
|
|
8875
|
+
await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(target), { recursive: true });
|
|
8558
8876
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
8559
8877
|
const fd = await import_node_fs28.promises.open(tmp, "w");
|
|
8560
8878
|
try {
|
|
@@ -8567,7 +8885,7 @@ async function writeAtomically(target, contents) {
|
|
|
8567
8885
|
}
|
|
8568
8886
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
8569
8887
|
const deadline = Date.now() + timeoutMs;
|
|
8570
|
-
await import_node_fs28.promises.mkdir(
|
|
8888
|
+
await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(lockPath), { recursive: true });
|
|
8571
8889
|
let probedHolder = false;
|
|
8572
8890
|
while (true) {
|
|
8573
8891
|
try {
|
|
@@ -8620,10 +8938,10 @@ async function readRegistry() {
|
|
|
8620
8938
|
throw err;
|
|
8621
8939
|
}
|
|
8622
8940
|
const parsed = JSON.parse(raw);
|
|
8623
|
-
return
|
|
8941
|
+
return import_types33.RegistryFileSchema.parse(parsed);
|
|
8624
8942
|
}
|
|
8625
8943
|
async function writeRegistry(reg) {
|
|
8626
|
-
const validated =
|
|
8944
|
+
const validated = import_types33.RegistryFileSchema.parse(reg);
|
|
8627
8945
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
8628
8946
|
}
|
|
8629
8947
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -8818,7 +9136,7 @@ init_auth();
|
|
|
8818
9136
|
// src/connectors-config.ts
|
|
8819
9137
|
init_cjs_shims();
|
|
8820
9138
|
var import_node_os4 = __toESM(require("os"), 1);
|
|
8821
|
-
var
|
|
9139
|
+
var import_node_path47 = __toESM(require("path"), 1);
|
|
8822
9140
|
var import_node_fs29 = require("fs");
|
|
8823
9141
|
var CONNECTORS_CONFIG_VERSION = 1;
|
|
8824
9142
|
var EnvRefUnsetError = class extends Error {
|
|
@@ -8833,11 +9151,11 @@ var EnvRefUnsetError = class extends Error {
|
|
|
8833
9151
|
};
|
|
8834
9152
|
function neatHome2() {
|
|
8835
9153
|
const override = process.env.NEAT_HOME;
|
|
8836
|
-
if (override && override.length > 0) return
|
|
8837
|
-
return
|
|
9154
|
+
if (override && override.length > 0) return import_node_path47.default.resolve(override);
|
|
9155
|
+
return import_node_path47.default.join(import_node_os4.default.homedir(), ".neat");
|
|
8838
9156
|
}
|
|
8839
9157
|
function connectorsConfigPath(home = neatHome2()) {
|
|
8840
|
-
return
|
|
9158
|
+
return import_node_path47.default.join(home, "connectors.json");
|
|
8841
9159
|
}
|
|
8842
9160
|
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
8843
9161
|
async function warnIfModeLooserThan0600(file) {
|
|
@@ -9173,11 +9491,11 @@ function registerRoutes(scope, ctx) {
|
|
|
9173
9491
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
9174
9492
|
const parsed = [];
|
|
9175
9493
|
for (const c of candidates) {
|
|
9176
|
-
const r =
|
|
9494
|
+
const r = import_types34.DivergenceTypeSchema.safeParse(c);
|
|
9177
9495
|
if (!r.success) {
|
|
9178
9496
|
return reply.code(400).send({
|
|
9179
9497
|
error: `unknown divergence type "${c}"`,
|
|
9180
|
-
allowed:
|
|
9498
|
+
allowed: import_types34.DivergenceTypeSchema.options
|
|
9181
9499
|
});
|
|
9182
9500
|
}
|
|
9183
9501
|
parsed.push(r.data);
|
|
@@ -9448,7 +9766,7 @@ function registerRoutes(scope, ctx) {
|
|
|
9448
9766
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
9449
9767
|
let violations = await log.readAll();
|
|
9450
9768
|
if (req.query.severity) {
|
|
9451
|
-
const sev =
|
|
9769
|
+
const sev = import_types34.PolicySeveritySchema.safeParse(req.query.severity);
|
|
9452
9770
|
if (!sev.success) {
|
|
9453
9771
|
return reply.code(400).send({
|
|
9454
9772
|
error: "invalid severity",
|
|
@@ -9487,7 +9805,7 @@ function registerRoutes(scope, ctx) {
|
|
|
9487
9805
|
scope.post("/policies/check", async (req, reply) => {
|
|
9488
9806
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
9489
9807
|
if (!proj) return;
|
|
9490
|
-
const parsed =
|
|
9808
|
+
const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
9491
9809
|
if (!parsed.success) {
|
|
9492
9810
|
return reply.code(400).send({
|
|
9493
9811
|
error: "invalid /policies/check body",
|
|
@@ -9559,6 +9877,42 @@ function registerRoutes(scope, ctx) {
|
|
|
9559
9877
|
return reply.code(500).send({ error: err.message });
|
|
9560
9878
|
}
|
|
9561
9879
|
});
|
|
9880
|
+
scope.get("/instrumentation", async (req, reply) => {
|
|
9881
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
9882
|
+
if (!proj) return;
|
|
9883
|
+
if (!proj.scanPath) {
|
|
9884
|
+
return { engaged: null };
|
|
9885
|
+
}
|
|
9886
|
+
try {
|
|
9887
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
9888
|
+
const uninstrumented = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
9889
|
+
if (state.hookFiles.length === 0) {
|
|
9890
|
+
return {
|
|
9891
|
+
engaged: false,
|
|
9892
|
+
diagnosis: {
|
|
9893
|
+
reason: "No instrumented entry point found \u2014 NEAT hasn't written an OTel init hook for this project.",
|
|
9894
|
+
fixCommand: "neat init",
|
|
9895
|
+
detail: "Run `neat init` so NEAT writes the instrumentation hook next to your entry point, then run your app."
|
|
9896
|
+
}
|
|
9897
|
+
};
|
|
9898
|
+
}
|
|
9899
|
+
if (uninstrumented.length > 0) {
|
|
9900
|
+
const names = uninstrumented.map((u) => u.library);
|
|
9901
|
+
const more = names.length > 1 ? ` (and ${names.length - 1} more)` : "";
|
|
9902
|
+
return {
|
|
9903
|
+
engaged: false,
|
|
9904
|
+
diagnosis: {
|
|
9905
|
+
reason: `\`${names[0]}\`${more} isn't in the auto-instrumentation set, so spans from it won't reach the graph.`,
|
|
9906
|
+
fixCommand: "neat extend",
|
|
9907
|
+
detail: `Uninstrumented on your hot path: ${names.join(", ")}. Run \`neat extend\` to wire the missing instrumentation.`
|
|
9908
|
+
}
|
|
9909
|
+
};
|
|
9910
|
+
}
|
|
9911
|
+
return { engaged: true };
|
|
9912
|
+
} catch {
|
|
9913
|
+
return { engaged: null };
|
|
9914
|
+
}
|
|
9915
|
+
});
|
|
9562
9916
|
scope.post("/extend/apply", async (req, reply) => {
|
|
9563
9917
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
9564
9918
|
if (!proj) return;
|
|
@@ -9753,21 +10107,21 @@ init_otel_grpc();
|
|
|
9753
10107
|
// src/daemon.ts
|
|
9754
10108
|
init_cjs_shims();
|
|
9755
10109
|
var import_node_fs31 = require("fs");
|
|
9756
|
-
var
|
|
10110
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
9757
10111
|
var import_node_module = require("module");
|
|
9758
10112
|
init_otel();
|
|
9759
10113
|
|
|
9760
10114
|
// src/connectors/index.ts
|
|
9761
10115
|
init_cjs_shims();
|
|
9762
|
-
var
|
|
10116
|
+
var import_types35 = require("@neat.is/types");
|
|
9763
10117
|
var NO_ENV = "unknown";
|
|
9764
10118
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
9765
10119
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
9766
10120
|
const sites = [];
|
|
9767
10121
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
9768
10122
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
9769
|
-
if (edge.provenance !==
|
|
9770
|
-
const parsed = (0,
|
|
10123
|
+
if (edge.provenance !== import_types35.Provenance.EXTRACTED) continue;
|
|
10124
|
+
const parsed = (0, import_types35.parseFileId)(edge.source);
|
|
9771
10125
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
9772
10126
|
const site = { relPath: edge.evidence.file };
|
|
9773
10127
|
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
@@ -9778,7 +10132,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
9778
10132
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
9779
10133
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
9780
10134
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
9781
|
-
if (attrs.type !==
|
|
10135
|
+
if (attrs.type !== import_types35.NodeType.RouteNode || !attrs.path) return void 0;
|
|
9782
10136
|
const site = { relPath: attrs.path };
|
|
9783
10137
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
9784
10138
|
return site;
|
|
@@ -9952,7 +10306,12 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
|
|
|
9952
10306
|
supabase: { capacity: 30, refillMs: 1e4 },
|
|
9953
10307
|
// Not a documented API limit at all — a self-imposed ceiling on the raw
|
|
9954
10308
|
// pg_stat_statements connection (see module header above).
|
|
9955
|
-
"supabase-postgres": { capacity: 20, refillMs: 3e3 }
|
|
10309
|
+
"supabase-postgres": { capacity: 20, refillMs: 3e3 },
|
|
10310
|
+
// Push provider (ADR-146): the Drains REST API is touched only by `neat
|
|
10311
|
+
// connector add/remove/test` (provision/deprovision/validate), never a poll
|
|
10312
|
+
// loop, so this bucket is exercised a handful of times per command. Kept
|
|
10313
|
+
// conservative pending a documented Drains-API rate limit.
|
|
10314
|
+
vercel: { capacity: 20, refillMs: 5e3 }
|
|
9956
10315
|
};
|
|
9957
10316
|
var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
|
|
9958
10317
|
function defaultRateLimitFor(provider) {
|
|
@@ -10244,10 +10603,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
10244
10603
|
// src/connectors/supabase/map.ts
|
|
10245
10604
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
10246
10605
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
10247
|
-
function targetFromRestPath(
|
|
10248
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
10606
|
+
function targetFromRestPath(path52) {
|
|
10607
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path52);
|
|
10249
10608
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
10250
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
10609
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path52);
|
|
10251
10610
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
10252
10611
|
return null;
|
|
10253
10612
|
}
|
|
@@ -10356,23 +10715,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
10356
10715
|
|
|
10357
10716
|
// src/connectors/supabase/resolve.ts
|
|
10358
10717
|
init_cjs_shims();
|
|
10359
|
-
var
|
|
10718
|
+
var import_types37 = require("@neat.is/types");
|
|
10360
10719
|
function createSupabaseResolveTarget(graph, config) {
|
|
10361
10720
|
return (signal, _ctx) => {
|
|
10362
10721
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
10363
10722
|
return null;
|
|
10364
10723
|
}
|
|
10365
|
-
const subResourceId = (0,
|
|
10724
|
+
const subResourceId = (0, import_types37.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
10366
10725
|
if (graph.hasNode(subResourceId)) {
|
|
10367
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
10726
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
|
|
10368
10727
|
}
|
|
10369
|
-
const bareResourceId = (0,
|
|
10728
|
+
const bareResourceId = (0, import_types37.infraId)(signal.targetKind, signal.targetName);
|
|
10370
10729
|
if (graph.hasNode(bareResourceId)) {
|
|
10371
|
-
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType:
|
|
10730
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
|
|
10372
10731
|
}
|
|
10373
|
-
const projectLevelId = (0,
|
|
10732
|
+
const projectLevelId = (0, import_types37.infraId)("supabase", config.nodeRef);
|
|
10374
10733
|
if (graph.hasNode(projectLevelId)) {
|
|
10375
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
10734
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
|
|
10376
10735
|
}
|
|
10377
10736
|
return null;
|
|
10378
10737
|
};
|
|
@@ -10465,7 +10824,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
10465
10824
|
|
|
10466
10825
|
// src/connectors/railway/index.ts
|
|
10467
10826
|
init_cjs_shims();
|
|
10468
|
-
var
|
|
10827
|
+
var import_types41 = require("@neat.is/types");
|
|
10469
10828
|
|
|
10470
10829
|
// src/connectors/railway/client.ts
|
|
10471
10830
|
init_cjs_shims();
|
|
@@ -10591,7 +10950,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
10591
10950
|
const out = [];
|
|
10592
10951
|
graph.forEachNode((_id, attrs) => {
|
|
10593
10952
|
const node = attrs;
|
|
10594
|
-
if (node.type !==
|
|
10953
|
+
if (node.type !== import_types41.NodeType.RouteNode) return;
|
|
10595
10954
|
const route = attrs;
|
|
10596
10955
|
if (route.service !== serviceName) return;
|
|
10597
10956
|
out.push({
|
|
@@ -10695,12 +11054,12 @@ function createRailwayResolveTarget(config) {
|
|
|
10695
11054
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
10696
11055
|
if (!serviceName) return null;
|
|
10697
11056
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
10698
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
11057
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types41.EdgeType.CALLS };
|
|
10699
11058
|
}
|
|
10700
11059
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
10701
11060
|
const peerName = config.serviceNameById[signal.targetName];
|
|
10702
11061
|
if (!peerName) return null;
|
|
10703
|
-
return { targetNodeId: (0,
|
|
11062
|
+
return { targetNodeId: (0, import_types41.serviceId)(peerName), serviceName, edgeType: import_types41.EdgeType.CONNECTS_TO };
|
|
10704
11063
|
}
|
|
10705
11064
|
return null;
|
|
10706
11065
|
};
|
|
@@ -10824,9 +11183,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
10824
11183
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
10825
11184
|
if (secondSep === -1) return null;
|
|
10826
11185
|
const method = rest.slice(0, secondSep);
|
|
10827
|
-
const
|
|
10828
|
-
if (!resourceName || !method || !
|
|
10829
|
-
return { resourceName, method, path:
|
|
11186
|
+
const path52 = rest.slice(secondSep + 1);
|
|
11187
|
+
if (!resourceName || !method || !path52) return null;
|
|
11188
|
+
return { resourceName, method, path: path52 };
|
|
10830
11189
|
}
|
|
10831
11190
|
function resourceNameFor(type, labels) {
|
|
10832
11191
|
if (!labels) return null;
|
|
@@ -10864,14 +11223,14 @@ function mapLogEntryToSignal(entry) {
|
|
|
10864
11223
|
if (!req) return null;
|
|
10865
11224
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
10866
11225
|
const method = req.requestMethod.toUpperCase();
|
|
10867
|
-
const
|
|
10868
|
-
if (
|
|
11226
|
+
const path52 = pathFromRequestUrl(req.requestUrl);
|
|
11227
|
+
if (path52 === null) return null;
|
|
10869
11228
|
const timestamp = entry.timestamp;
|
|
10870
11229
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
10871
11230
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
10872
11231
|
return {
|
|
10873
11232
|
targetKind: resourceType,
|
|
10874
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
11233
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path52 }),
|
|
10875
11234
|
callCount: 1,
|
|
10876
11235
|
errorCount: isError ? 1 : 0,
|
|
10877
11236
|
lastObservedIso: timestamp
|
|
@@ -10888,7 +11247,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
10888
11247
|
|
|
10889
11248
|
// src/connectors/firebase/resolve.ts
|
|
10890
11249
|
init_cjs_shims();
|
|
10891
|
-
var
|
|
11250
|
+
var import_types42 = require("@neat.is/types");
|
|
10892
11251
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
10893
11252
|
switch (resourceType) {
|
|
10894
11253
|
case "cloud_function":
|
|
@@ -10903,7 +11262,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
10903
11262
|
const entries = [];
|
|
10904
11263
|
graph.forEachNode((_id, attrs) => {
|
|
10905
11264
|
const node = attrs;
|
|
10906
|
-
if (node.type !==
|
|
11265
|
+
if (node.type !== import_types42.NodeType.RouteNode) return;
|
|
10907
11266
|
const route = attrs;
|
|
10908
11267
|
if (route.service !== serviceName) return;
|
|
10909
11268
|
entries.push({
|
|
@@ -10935,7 +11294,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
10935
11294
|
return {
|
|
10936
11295
|
targetNodeId: match.routeNodeId,
|
|
10937
11296
|
serviceName,
|
|
10938
|
-
edgeType:
|
|
11297
|
+
edgeType: import_types42.EdgeType.CALLS
|
|
10939
11298
|
};
|
|
10940
11299
|
};
|
|
10941
11300
|
}
|
|
@@ -10962,7 +11321,7 @@ init_cjs_shims();
|
|
|
10962
11321
|
|
|
10963
11322
|
// src/connectors/cloudflare/connector.ts
|
|
10964
11323
|
init_cjs_shims();
|
|
10965
|
-
var
|
|
11324
|
+
var import_types44 = require("@neat.is/types");
|
|
10966
11325
|
|
|
10967
11326
|
// src/connectors/cloudflare/client.ts
|
|
10968
11327
|
init_cjs_shims();
|
|
@@ -11078,7 +11437,7 @@ function mapEventToSignal(event) {
|
|
|
11078
11437
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
11079
11438
|
const statusCode = metadata?.statusCode;
|
|
11080
11439
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
11081
|
-
const
|
|
11440
|
+
const path52 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
11082
11441
|
return {
|
|
11083
11442
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
11084
11443
|
targetName: scriptName,
|
|
@@ -11086,7 +11445,7 @@ function mapEventToSignal(event) {
|
|
|
11086
11445
|
errorCount: isError ? 1 : 0,
|
|
11087
11446
|
lastObservedIso: observedAt.toISOString(),
|
|
11088
11447
|
method,
|
|
11089
|
-
...
|
|
11448
|
+
...path52 ? { path: path52 } : {},
|
|
11090
11449
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
11091
11450
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
11092
11451
|
};
|
|
@@ -11126,19 +11485,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
11126
11485
|
graph.forEachNode((id, attrs) => {
|
|
11127
11486
|
if (found) return;
|
|
11128
11487
|
const a = attrs;
|
|
11129
|
-
if (a.type ===
|
|
11488
|
+
if (a.type === import_types44.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
11130
11489
|
found = id;
|
|
11131
11490
|
}
|
|
11132
11491
|
});
|
|
11133
11492
|
return found;
|
|
11134
11493
|
}
|
|
11135
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
11136
|
-
const normalizedPath = normalizePathTemplate(
|
|
11494
|
+
function findMatchingRouteNode(graph, serviceName, method, path52) {
|
|
11495
|
+
const normalizedPath = normalizePathTemplate(path52);
|
|
11137
11496
|
let found = null;
|
|
11138
11497
|
graph.forEachNode((id, attrs) => {
|
|
11139
11498
|
if (found) return;
|
|
11140
11499
|
const a = attrs;
|
|
11141
|
-
if (a.type !==
|
|
11500
|
+
if (a.type !== import_types44.NodeType.RouteNode || a.service !== serviceName) return;
|
|
11142
11501
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
11143
11502
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
11144
11503
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -11150,18 +11509,18 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
11150
11509
|
return (signal) => {
|
|
11151
11510
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
11152
11511
|
const scriptName = signal.targetName;
|
|
11153
|
-
const { method, path:
|
|
11512
|
+
const { method, path: path52 } = signal;
|
|
11154
11513
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
11155
|
-
if (!method || !
|
|
11156
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
11514
|
+
if (!method || !path52) return wholeFileId;
|
|
11515
|
+
return findMatchingRouteNode(graph, serviceName, method, path52) ?? wholeFileId;
|
|
11157
11516
|
};
|
|
11158
11517
|
const mapping = config.workers?.[scriptName];
|
|
11159
11518
|
if (mapping) {
|
|
11160
|
-
const wholeFileId = (0,
|
|
11519
|
+
const wholeFileId = (0, import_types44.fileId)(mapping.service, mapping.entryFile);
|
|
11161
11520
|
return {
|
|
11162
11521
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
11163
11522
|
serviceName: mapping.service,
|
|
11164
|
-
edgeType:
|
|
11523
|
+
edgeType: import_types44.EdgeType.CALLS
|
|
11165
11524
|
};
|
|
11166
11525
|
}
|
|
11167
11526
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -11170,18 +11529,124 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
11170
11529
|
return {
|
|
11171
11530
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
11172
11531
|
serviceName: fileNode.service,
|
|
11173
|
-
edgeType:
|
|
11532
|
+
edgeType: import_types44.EdgeType.CALLS
|
|
11174
11533
|
};
|
|
11175
11534
|
}
|
|
11176
11535
|
return {
|
|
11177
|
-
targetNodeId: (0,
|
|
11536
|
+
targetNodeId: (0, import_types44.infraId)("cloudflare-worker", scriptName),
|
|
11178
11537
|
serviceName: scriptName,
|
|
11179
|
-
edgeType:
|
|
11538
|
+
edgeType: import_types44.EdgeType.CALLS,
|
|
11180
11539
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
11181
11540
|
};
|
|
11182
11541
|
};
|
|
11183
11542
|
}
|
|
11184
11543
|
|
|
11544
|
+
// src/connectors/vercel/index.ts
|
|
11545
|
+
init_cjs_shims();
|
|
11546
|
+
|
|
11547
|
+
// src/connectors/vercel/client.ts
|
|
11548
|
+
init_cjs_shims();
|
|
11549
|
+
var DEFAULT_API_BASE_URL = "https://api.vercel.com";
|
|
11550
|
+
var DEFAULT_DRAIN_NAME = "neat-otlp";
|
|
11551
|
+
var TRACE_SCHEMAS = { trace: { version: "v1" } };
|
|
11552
|
+
function apiBase(config) {
|
|
11553
|
+
return config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
11554
|
+
}
|
|
11555
|
+
function teamQuery(config) {
|
|
11556
|
+
return `?teamId=${encodeURIComponent(config.teamId)}`;
|
|
11557
|
+
}
|
|
11558
|
+
function drainDelivery(config, otelToken) {
|
|
11559
|
+
return {
|
|
11560
|
+
type: "http",
|
|
11561
|
+
endpoint: config.endpoint,
|
|
11562
|
+
encoding: "json",
|
|
11563
|
+
headers: bearerAuthHeader(otelToken),
|
|
11564
|
+
...config.secret ? { secret: config.secret } : {}
|
|
11565
|
+
};
|
|
11566
|
+
}
|
|
11567
|
+
async function describeError(res) {
|
|
11568
|
+
try {
|
|
11569
|
+
const data = await res.json();
|
|
11570
|
+
const message = data?.error?.message;
|
|
11571
|
+
return typeof message === "string" && message.length > 0 ? ` \u2014 ${message}` : "";
|
|
11572
|
+
} catch {
|
|
11573
|
+
return "";
|
|
11574
|
+
}
|
|
11575
|
+
}
|
|
11576
|
+
async function createVercelDrain(config, credentials, fetchImpl = fetch) {
|
|
11577
|
+
const projectIds = config.projectIds ?? [];
|
|
11578
|
+
const body = {
|
|
11579
|
+
name: config.drainName ?? DEFAULT_DRAIN_NAME,
|
|
11580
|
+
projects: projectIds.length > 0 ? "some" : "all",
|
|
11581
|
+
...projectIds.length > 0 ? { projectIds } : {},
|
|
11582
|
+
schemas: TRACE_SCHEMAS,
|
|
11583
|
+
delivery: drainDelivery(config, credentials.otelToken),
|
|
11584
|
+
source: { kind: "self-served" }
|
|
11585
|
+
};
|
|
11586
|
+
const res = await junctionFetch(
|
|
11587
|
+
`${apiBase(config)}/v1/drains${teamQuery(config)}`,
|
|
11588
|
+
{
|
|
11589
|
+
method: "POST",
|
|
11590
|
+
headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
|
|
11591
|
+
body: JSON.stringify(body)
|
|
11592
|
+
},
|
|
11593
|
+
{ provider: "vercel", accountKey: config.teamId, fetchImpl }
|
|
11594
|
+
);
|
|
11595
|
+
if (!res.ok) {
|
|
11596
|
+
throw new Error(
|
|
11597
|
+
`vercel connector: create drain failed (${res.status} ${res.statusText}${await describeError(res)})`
|
|
11598
|
+
);
|
|
11599
|
+
}
|
|
11600
|
+
const payload = await res.json().catch(() => null);
|
|
11601
|
+
if (!payload || typeof payload.id !== "string" || payload.id.length === 0) {
|
|
11602
|
+
throw new Error(
|
|
11603
|
+
"vercel connector: create drain returned no drain id \u2014 the Drains API response shape may have changed"
|
|
11604
|
+
);
|
|
11605
|
+
}
|
|
11606
|
+
return {
|
|
11607
|
+
id: payload.id,
|
|
11608
|
+
...payload.status ? { status: payload.status } : {},
|
|
11609
|
+
...payload.disabledReason ? { disabledReason: payload.disabledReason } : {}
|
|
11610
|
+
};
|
|
11611
|
+
}
|
|
11612
|
+
async function deleteVercelDrain(config, drainId, credentials, fetchImpl = fetch) {
|
|
11613
|
+
const res = await junctionFetch(
|
|
11614
|
+
`${apiBase(config)}/v1/drains/${encodeURIComponent(drainId)}${teamQuery(config)}`,
|
|
11615
|
+
{ method: "DELETE", headers: { ...bearerAuthHeader(credentials.token) } },
|
|
11616
|
+
{ provider: "vercel", accountKey: config.teamId, fetchImpl }
|
|
11617
|
+
);
|
|
11618
|
+
if (res.ok || res.status === 404) return;
|
|
11619
|
+
throw new Error(
|
|
11620
|
+
`vercel connector: delete drain failed (${res.status} ${res.statusText}${await describeError(res)})`
|
|
11621
|
+
);
|
|
11622
|
+
}
|
|
11623
|
+
async function testVercelDrainDelivery(config, credentials, fetchImpl = fetch) {
|
|
11624
|
+
const res = await junctionFetch(
|
|
11625
|
+
`${apiBase(config)}/v1/drains/test${teamQuery(config)}`,
|
|
11626
|
+
{
|
|
11627
|
+
method: "POST",
|
|
11628
|
+
headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
|
|
11629
|
+
body: JSON.stringify({ schemas: TRACE_SCHEMAS, delivery: drainDelivery(config, credentials.otelToken) })
|
|
11630
|
+
},
|
|
11631
|
+
{ provider: "vercel", accountKey: config.teamId, fetchImpl }
|
|
11632
|
+
);
|
|
11633
|
+
if (res.status === 401 || res.status === 403) {
|
|
11634
|
+
return { status: "failure", error: `vercel rejected the API token (HTTP ${res.status})` };
|
|
11635
|
+
}
|
|
11636
|
+
if (!res.ok) {
|
|
11637
|
+
return {
|
|
11638
|
+
status: "failure",
|
|
11639
|
+
error: `vercel drain validation failed (${res.status} ${res.statusText}${await describeError(res)})`
|
|
11640
|
+
};
|
|
11641
|
+
}
|
|
11642
|
+
const payload = await res.json().catch(() => null);
|
|
11643
|
+
return {
|
|
11644
|
+
...payload?.status ? { status: payload.status } : {},
|
|
11645
|
+
...payload?.error ? { error: payload.error } : {},
|
|
11646
|
+
...payload?.endpoint ? { endpoint: payload.endpoint } : {}
|
|
11647
|
+
};
|
|
11648
|
+
}
|
|
11649
|
+
|
|
11185
11650
|
// src/connectors/registry.ts
|
|
11186
11651
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
11187
11652
|
async function authProbe(input) {
|
|
@@ -11318,21 +11783,112 @@ var PROVIDER_DISPATCH = {
|
|
|
11318
11783
|
resolveTarget: createCloudflareResolveTarget(config, graph)
|
|
11319
11784
|
};
|
|
11320
11785
|
},
|
|
11321
|
-
// GET /
|
|
11322
|
-
//
|
|
11786
|
+
// GET /accounts/{accountId}/tokens/verify — the *account-scoped* token-verify
|
|
11787
|
+
// endpoint. A Workers connector token is scoped to the account, and the
|
|
11788
|
+
// user-level `GET /user/tokens/verify` returns 401 "Invalid API Token" for
|
|
11789
|
+
// such a token even though it authenticates fine against the account's own
|
|
11790
|
+
// resources (confirmed live). Probing the account-scoped verify endpoint —
|
|
11791
|
+
// `accountId` is already required for this provider — returns 200
|
|
11792
|
+
// `{status:"active"}` for a working token and 401 for a bad one, so a valid
|
|
11793
|
+
// Workers token is no longer falsely rejected at `neat connector add`.
|
|
11323
11794
|
validate({ credentials, options, fetchImpl }) {
|
|
11324
11795
|
const cfg = options;
|
|
11325
11796
|
const baseUrl = cfg.baseUrl ?? CLOUDFLARE_API_BASE_URL;
|
|
11326
11797
|
return authProbe({
|
|
11327
11798
|
provider: "cloudflare",
|
|
11328
11799
|
accountKey: cfg.accountId ?? "validate",
|
|
11329
|
-
url: `${baseUrl}/
|
|
11800
|
+
url: `${baseUrl}/accounts/${cfg.accountId ?? ""}/tokens/verify`,
|
|
11330
11801
|
token: String(credentials.apiToken ?? ""),
|
|
11331
11802
|
...fetchImpl ? { fetchImpl } : {}
|
|
11332
11803
|
});
|
|
11333
11804
|
}
|
|
11334
11805
|
}
|
|
11335
11806
|
};
|
|
11807
|
+
function vercelCredsFrom(credentials) {
|
|
11808
|
+
return { token: String(credentials.token ?? ""), otelToken: String(credentials.otelToken ?? "") };
|
|
11809
|
+
}
|
|
11810
|
+
function vercelConfigFromOptions(options) {
|
|
11811
|
+
const raw = options.projectIds;
|
|
11812
|
+
let projectIds;
|
|
11813
|
+
if (Array.isArray(raw)) projectIds = raw.filter((p) => typeof p === "string");
|
|
11814
|
+
else if (typeof raw === "string" && raw.trim().length > 0) {
|
|
11815
|
+
projectIds = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
11816
|
+
}
|
|
11817
|
+
return {
|
|
11818
|
+
teamId: String(options.teamId ?? ""),
|
|
11819
|
+
endpoint: String(options.endpoint ?? ""),
|
|
11820
|
+
...projectIds && projectIds.length > 0 ? { projectIds } : {},
|
|
11821
|
+
...typeof options.drainId === "string" ? { drainId: options.drainId } : {},
|
|
11822
|
+
...typeof options.drainName === "string" ? { drainName: options.drainName } : {},
|
|
11823
|
+
...typeof options.apiBaseUrl === "string" ? { apiBaseUrl: options.apiBaseUrl } : {},
|
|
11824
|
+
...typeof options.secret === "string" ? { secret: options.secret } : {}
|
|
11825
|
+
};
|
|
11826
|
+
}
|
|
11827
|
+
var PUSH_PROVIDER_DISPATCH = {
|
|
11828
|
+
vercel: {
|
|
11829
|
+
provider: "vercel",
|
|
11830
|
+
// The Vercel access token is the "primary" secret a single `--token`
|
|
11831
|
+
// populates; `otelToken` (the daemon's OTLP bearer) is the second field.
|
|
11832
|
+
primaryCredentialKey: "token",
|
|
11833
|
+
requiredCredentialFields: ["token", "otelToken"],
|
|
11834
|
+
// teamId scopes every Drains call; endpoint is where the drain delivers.
|
|
11835
|
+
// projectIds is optional (absent → the drain covers the whole team).
|
|
11836
|
+
requiredOptionFields: ["teamId", "endpoint"],
|
|
11837
|
+
// POST /v1/drains/test — authenticates the token and pings the endpoint
|
|
11838
|
+
// with a sample event, so `success` means the credential is live *and* the
|
|
11839
|
+
// daemon's OTLP endpoint is reachable and accepted the drain's bearer.
|
|
11840
|
+
async validate({ credentials, options, fetchImpl }) {
|
|
11841
|
+
const result = await testVercelDrainDelivery(
|
|
11842
|
+
vercelConfigFromOptions(options),
|
|
11843
|
+
vercelCredsFrom(credentials),
|
|
11844
|
+
fetchImpl
|
|
11845
|
+
);
|
|
11846
|
+
if (result.status === "success") return { ok: true };
|
|
11847
|
+
return {
|
|
11848
|
+
ok: false,
|
|
11849
|
+
reason: result.error ?? `vercel drain delivery test returned "${result.status ?? "no status"}"`
|
|
11850
|
+
};
|
|
11851
|
+
},
|
|
11852
|
+
// POST /v1/drains — creates the trace drain, returns its id to store in
|
|
11853
|
+
// `options.drainId`. A created-but-not-enabled drain is surfaced as a note,
|
|
11854
|
+
// not a failure (the entry still points at a real drain).
|
|
11855
|
+
async provision({ credentials, options, fetchImpl }) {
|
|
11856
|
+
try {
|
|
11857
|
+
const created = await createVercelDrain(
|
|
11858
|
+
vercelConfigFromOptions(options),
|
|
11859
|
+
vercelCredsFrom(credentials),
|
|
11860
|
+
fetchImpl
|
|
11861
|
+
);
|
|
11862
|
+
const note = created.status && created.status !== "enabled" ? `the drain was created but its status is "${created.status}"${created.disabledReason ? ` (${created.disabledReason})` : ""} \u2014 check the Vercel dashboard` : void 0;
|
|
11863
|
+
return { ok: true, options: { drainId: created.id }, ...note ? { note } : {} };
|
|
11864
|
+
} catch (err) {
|
|
11865
|
+
return { ok: false, reason: err.message };
|
|
11866
|
+
}
|
|
11867
|
+
},
|
|
11868
|
+
// DELETE /v1/drains/{id} — idempotent (deleteVercelDrain treats 404 as
|
|
11869
|
+
// success). No recorded drainId → nothing to delete, still a success.
|
|
11870
|
+
async deprovision({ credentials, options, fetchImpl }) {
|
|
11871
|
+
const drainId = typeof options.drainId === "string" ? options.drainId : "";
|
|
11872
|
+
if (!drainId) {
|
|
11873
|
+
return { ok: true, note: "no drain id was recorded \u2014 nothing to delete on the Vercel side" };
|
|
11874
|
+
}
|
|
11875
|
+
try {
|
|
11876
|
+
await deleteVercelDrain(
|
|
11877
|
+
vercelConfigFromOptions(options),
|
|
11878
|
+
drainId,
|
|
11879
|
+
vercelCredsFrom(credentials),
|
|
11880
|
+
fetchImpl
|
|
11881
|
+
);
|
|
11882
|
+
return { ok: true };
|
|
11883
|
+
} catch (err) {
|
|
11884
|
+
return { ok: false, reason: err.message };
|
|
11885
|
+
}
|
|
11886
|
+
}
|
|
11887
|
+
}
|
|
11888
|
+
};
|
|
11889
|
+
function isPushProvider(provider) {
|
|
11890
|
+
return provider in PUSH_PROVIDER_DISPATCH;
|
|
11891
|
+
}
|
|
11336
11892
|
function resolveEntryCredentials(dispatch, entry, env) {
|
|
11337
11893
|
let credentials;
|
|
11338
11894
|
try {
|
|
@@ -11355,6 +11911,13 @@ function resolveEntryCredentials(dispatch, entry, env) {
|
|
|
11355
11911
|
function buildRegistration(entry, graph, env = process.env) {
|
|
11356
11912
|
const dispatch = PROVIDER_DISPATCH[entry.provider];
|
|
11357
11913
|
if (!dispatch) {
|
|
11914
|
+
if (isPushProvider(entry.provider)) {
|
|
11915
|
+
return {
|
|
11916
|
+
ok: false,
|
|
11917
|
+
push: true,
|
|
11918
|
+
reason: `push provider "${entry.provider}" ingests via the OTLP receiver \u2014 nothing to poll`
|
|
11919
|
+
};
|
|
11920
|
+
}
|
|
11358
11921
|
return { ok: false, reason: `unknown provider "${entry.provider}"` };
|
|
11359
11922
|
}
|
|
11360
11923
|
const creds = resolveEntryCredentials(dispatch, entry, env);
|
|
@@ -11405,7 +11968,7 @@ async function loadConnectorRegistrations(input) {
|
|
|
11405
11968
|
if (!connectorMatchesProject(entry, project)) continue;
|
|
11406
11969
|
const result = buildRegistration(entry, graph, env);
|
|
11407
11970
|
if (result.ok) registrations.push(result.registration);
|
|
11408
|
-
else onSkip?.(entry, result.reason);
|
|
11971
|
+
else if (!result.push) onSkip?.(entry, result.reason);
|
|
11409
11972
|
}
|
|
11410
11973
|
return registrations;
|
|
11411
11974
|
}
|
|
@@ -11416,7 +11979,7 @@ init_auth();
|
|
|
11416
11979
|
// src/unrouted.ts
|
|
11417
11980
|
init_cjs_shims();
|
|
11418
11981
|
var import_node_fs30 = require("fs");
|
|
11419
|
-
var
|
|
11982
|
+
var import_node_path50 = __toESM(require("path"), 1);
|
|
11420
11983
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
11421
11984
|
return {
|
|
11422
11985
|
timestamp: now.toISOString(),
|
|
@@ -11426,34 +11989,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
11426
11989
|
};
|
|
11427
11990
|
}
|
|
11428
11991
|
async function appendUnroutedSpan(neatHome3, record) {
|
|
11429
|
-
const target =
|
|
11992
|
+
const target = import_node_path50.default.join(neatHome3, "errors.ndjson");
|
|
11430
11993
|
await import_node_fs30.promises.mkdir(neatHome3, { recursive: true });
|
|
11431
11994
|
await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
11432
11995
|
}
|
|
11433
11996
|
function unroutedErrorsPath(neatHome3) {
|
|
11434
|
-
return
|
|
11997
|
+
return import_node_path50.default.join(neatHome3, "errors.ndjson");
|
|
11435
11998
|
}
|
|
11436
11999
|
|
|
11437
12000
|
// src/daemon.ts
|
|
11438
|
-
var
|
|
12001
|
+
var import_types47 = require("@neat.is/types");
|
|
11439
12002
|
function daemonJsonPath(scanPath) {
|
|
11440
|
-
return
|
|
12003
|
+
return import_node_path51.default.join(scanPath, "neat-out", "daemon.json");
|
|
11441
12004
|
}
|
|
11442
12005
|
function daemonsDiscoveryDir(home) {
|
|
11443
12006
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
11444
|
-
return
|
|
12007
|
+
return import_node_path51.default.join(base, "daemons");
|
|
11445
12008
|
}
|
|
11446
12009
|
function daemonDiscoveryPath(project, home) {
|
|
11447
|
-
return
|
|
12010
|
+
return import_node_path51.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
11448
12011
|
}
|
|
11449
12012
|
function sanitizeDiscoveryName(project) {
|
|
11450
12013
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
11451
12014
|
}
|
|
11452
12015
|
function neatHomeFromEnv() {
|
|
11453
12016
|
const env = process.env.NEAT_HOME;
|
|
11454
|
-
if (env && env.length > 0) return
|
|
12017
|
+
if (env && env.length > 0) return import_node_path51.default.resolve(env);
|
|
11455
12018
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
11456
|
-
return
|
|
12019
|
+
return import_node_path51.default.join(home, ".neat");
|
|
11457
12020
|
}
|
|
11458
12021
|
function resolveNeatVersion() {
|
|
11459
12022
|
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
@@ -11508,11 +12071,11 @@ function teardownSlot(slot) {
|
|
|
11508
12071
|
}
|
|
11509
12072
|
}
|
|
11510
12073
|
function neatHomeFor(opts) {
|
|
11511
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
12074
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path51.default.resolve(opts.neatHome);
|
|
11512
12075
|
const env = process.env.NEAT_HOME;
|
|
11513
|
-
if (env && env.length > 0) return
|
|
12076
|
+
if (env && env.length > 0) return import_node_path51.default.resolve(env);
|
|
11514
12077
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
11515
|
-
return
|
|
12078
|
+
return import_node_path51.default.join(home, ".neat");
|
|
11516
12079
|
}
|
|
11517
12080
|
function routeSpanToProject(serviceName, projects) {
|
|
11518
12081
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -11556,11 +12119,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
11556
12119
|
if (!serviceName) return true;
|
|
11557
12120
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
11558
12121
|
return graph.someNode(
|
|
11559
|
-
(_id, attrs) => attrs.type ===
|
|
12122
|
+
(_id, attrs) => attrs.type === import_types47.NodeType.ServiceNode && attrs.name === serviceName
|
|
11560
12123
|
);
|
|
11561
12124
|
}
|
|
11562
12125
|
async function bootstrapProject(entry, connectors = [], neatHome3) {
|
|
11563
|
-
const paths = pathsForProject(entry.name,
|
|
12126
|
+
const paths = pathsForProject(entry.name, import_node_path51.default.join(entry.path, "neat-out"));
|
|
11564
12127
|
try {
|
|
11565
12128
|
const stat = await import_node_fs31.promises.stat(entry.path);
|
|
11566
12129
|
if (!stat.isDirectory()) {
|
|
@@ -11691,7 +12254,7 @@ async function startDaemon(opts = {}) {
|
|
|
11691
12254
|
const projectArg = typeof opts.project === "string" && opts.project.length > 0 ? opts.project : process.env.NEAT_PROJECT && process.env.NEAT_PROJECT.length > 0 ? process.env.NEAT_PROJECT : null;
|
|
11692
12255
|
const projectPathArg = opts.projectPath && opts.projectPath.length > 0 ? opts.projectPath : process.env.NEAT_PROJECT_PATH && process.env.NEAT_PROJECT_PATH.length > 0 ? process.env.NEAT_PROJECT_PATH : null;
|
|
11693
12256
|
const singleProject = projectArg;
|
|
11694
|
-
const singleProjectPath = singleProject && projectPathArg ?
|
|
12257
|
+
const singleProjectPath = singleProject && projectPathArg ? import_node_path51.default.resolve(projectPathArg) : null;
|
|
11695
12258
|
if (singleProject && !singleProjectPath) {
|
|
11696
12259
|
throw new Error(
|
|
11697
12260
|
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
@@ -11706,7 +12269,7 @@ async function startDaemon(opts = {}) {
|
|
|
11706
12269
|
);
|
|
11707
12270
|
}
|
|
11708
12271
|
}
|
|
11709
|
-
const pidPath =
|
|
12272
|
+
const pidPath = import_node_path51.default.join(home, "neatd.pid");
|
|
11710
12273
|
await writeAtomically(pidPath, `${process.pid}
|
|
11711
12274
|
`);
|
|
11712
12275
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -12113,8 +12676,8 @@ async function startDaemon(opts = {}) {
|
|
|
12113
12676
|
let registryWatcher = null;
|
|
12114
12677
|
let reloadTimer = null;
|
|
12115
12678
|
if (!singleProject) try {
|
|
12116
|
-
const regDir =
|
|
12117
|
-
const regBase =
|
|
12679
|
+
const regDir = import_node_path51.default.dirname(regPath);
|
|
12680
|
+
const regBase = import_node_path51.default.basename(regPath);
|
|
12118
12681
|
registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
|
|
12119
12682
|
if (filename !== null && filename !== regBase) return;
|
|
12120
12683
|
if (reloadTimer) clearTimeout(reloadTimer);
|