@neat.is/core 0.5.4-dev.20260721 → 0.6.1
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-VR73QNJD.js → chunk-3YUNROLT.js} +12 -6
- package/dist/{chunk-VR73QNJD.js.map → chunk-3YUNROLT.js.map} +1 -1
- package/dist/{chunk-PEFX3DBR.js → chunk-7C6ZULNU.js} +151 -9
- package/dist/chunk-7C6ZULNU.js.map +1 -0
- package/dist/{chunk-I4NZ7PSN.js → chunk-BZ3AJVAC.js} +8 -3
- package/dist/chunk-BZ3AJVAC.js.map +1 -0
- package/dist/{chunk-4RU3AAOI.js → chunk-MVINCLQM.js} +2 -2
- package/dist/cli.cjs +209 -17
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +49 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +164 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +164 -11
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-TEGOXE6T.js → otel-grpc-XS45HBET.js} +3 -3
- package/dist/server.cjs +155 -8
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-I4NZ7PSN.js.map +0 -1
- package/dist/chunk-PEFX3DBR.js.map +0 -1
- /package/dist/{chunk-4RU3AAOI.js.map → chunk-MVINCLQM.js.map} +0 -0
- /package/dist/{otel-grpc-TEGOXE6T.js.map → otel-grpc-XS45HBET.js.map} +0 -0
package/dist/neatd.js
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
import {
|
|
3
3
|
reconcileDaemonRecordSync,
|
|
4
4
|
startDaemon
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-3YUNROLT.js";
|
|
6
6
|
import {
|
|
7
7
|
listProjects,
|
|
8
8
|
registryPath
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7C6ZULNU.js";
|
|
10
10
|
import {
|
|
11
11
|
BindAuthorityError,
|
|
12
12
|
__require
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-BZ3AJVAC.js";
|
|
14
14
|
|
|
15
15
|
// src/neatd.ts
|
|
16
16
|
import { promises as fs } from "fs";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
reshapeGrpcRequest,
|
|
3
3
|
startOtelGrpcReceiver
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-MVINCLQM.js";
|
|
5
|
+
import "./chunk-BZ3AJVAC.js";
|
|
6
6
|
export {
|
|
7
7
|
reshapeGrpcRequest,
|
|
8
8
|
startOtelGrpcReceiver
|
|
9
9
|
};
|
|
10
|
-
//# sourceMappingURL=otel-grpc-
|
|
10
|
+
//# sourceMappingURL=otel-grpc-XS45HBET.js.map
|
package/dist/server.cjs
CHANGED
|
@@ -340,6 +340,11 @@ function pickEnv(spanAttrs, resourceAttrs) {
|
|
|
340
340
|
}
|
|
341
341
|
return ENV_FALLBACK;
|
|
342
342
|
}
|
|
343
|
+
function normalizeDbSystem(attrs) {
|
|
344
|
+
const raw = attrs["db.system"];
|
|
345
|
+
if (typeof raw !== "string") return void 0;
|
|
346
|
+
return raw === "mongoose" ? "mongodb" : raw;
|
|
347
|
+
}
|
|
343
348
|
function messagingDestinationOf(attrs) {
|
|
344
349
|
for (const key of ["messaging.destination.name", "messaging.destination"]) {
|
|
345
350
|
const v = attrs[key];
|
|
@@ -394,7 +399,7 @@ function parseOtlpRequest(body) {
|
|
|
394
399
|
durationNanos: durationNanos(span.startTimeUnixNano, span.endTimeUnixNano),
|
|
395
400
|
env: pickEnv(attrs, resourceAttrs),
|
|
396
401
|
attributes: attrs,
|
|
397
|
-
dbSystem:
|
|
402
|
+
dbSystem: normalizeDbSystem(attrs),
|
|
398
403
|
dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
|
|
399
404
|
dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
|
|
400
405
|
messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
|
|
@@ -5858,6 +5863,7 @@ init_cjs_shims();
|
|
|
5858
5863
|
var import_node_path24 = __toESM(require("path"), 1);
|
|
5859
5864
|
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
5860
5865
|
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5866
|
+
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
5861
5867
|
var import_types12 = require("@neat.is/types");
|
|
5862
5868
|
var PARSE_CHUNK2 = 16384;
|
|
5863
5869
|
function parseSource2(parser, source) {
|
|
@@ -5870,6 +5876,11 @@ function makeJsParser2() {
|
|
|
5870
5876
|
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
5871
5877
|
return p;
|
|
5872
5878
|
}
|
|
5879
|
+
function makePyParser2() {
|
|
5880
|
+
const p = new import_tree_sitter2.default();
|
|
5881
|
+
p.setLanguage(import_tree_sitter_python2.default);
|
|
5882
|
+
return p;
|
|
5883
|
+
}
|
|
5873
5884
|
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
5874
5885
|
"get",
|
|
5875
5886
|
"post",
|
|
@@ -5882,6 +5893,7 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
|
5882
5893
|
]);
|
|
5883
5894
|
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
5884
5895
|
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5896
|
+
var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
|
|
5885
5897
|
function canonicalizeTemplate(raw) {
|
|
5886
5898
|
let p = raw.split("?")[0].split("#")[0];
|
|
5887
5899
|
if (!p.startsWith("/")) p = "/" + p;
|
|
@@ -6109,8 +6121,102 @@ function nextRoutesFromFile(source, relFile, parser) {
|
|
|
6109
6121
|
}
|
|
6110
6122
|
return [];
|
|
6111
6123
|
}
|
|
6124
|
+
function pyStaticStringText(node) {
|
|
6125
|
+
if (node.type !== "string") return null;
|
|
6126
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6127
|
+
const child = node.namedChild(i);
|
|
6128
|
+
if (child?.type === "interpolation") return null;
|
|
6129
|
+
if (child?.type === "string_content") return child.text;
|
|
6130
|
+
}
|
|
6131
|
+
return "";
|
|
6132
|
+
}
|
|
6133
|
+
function keywordArrayStrings(argsNode, key) {
|
|
6134
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
6135
|
+
const arg = argsNode.namedChild(i);
|
|
6136
|
+
if (arg?.type !== "keyword_argument") continue;
|
|
6137
|
+
if (arg.childForFieldName("name")?.text !== key) continue;
|
|
6138
|
+
const val = arg.childForFieldName("value");
|
|
6139
|
+
if (!val || val.type !== "list") return [];
|
|
6140
|
+
const out = [];
|
|
6141
|
+
for (let j = 0; j < val.namedChildCount; j++) {
|
|
6142
|
+
const el = val.namedChild(j);
|
|
6143
|
+
if (el?.type === "string") {
|
|
6144
|
+
const s = pyStaticStringText(el);
|
|
6145
|
+
if (s) out.push(s);
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
return out;
|
|
6149
|
+
}
|
|
6150
|
+
return [];
|
|
6151
|
+
}
|
|
6152
|
+
function collectApiRouterPrefixes(root) {
|
|
6153
|
+
const prefixes = /* @__PURE__ */ new Map();
|
|
6154
|
+
walk(root, (node) => {
|
|
6155
|
+
if (node.type !== "assignment") return;
|
|
6156
|
+
const right = node.childForFieldName("right");
|
|
6157
|
+
if (!right || right.type !== "call") return;
|
|
6158
|
+
const fn = right.childForFieldName("function");
|
|
6159
|
+
if (!fn) return;
|
|
6160
|
+
const ctor = fn.type === "attribute" ? fn.childForFieldName("attribute")?.text : fn.text;
|
|
6161
|
+
if (ctor !== "APIRouter") return;
|
|
6162
|
+
const left = node.childForFieldName("left");
|
|
6163
|
+
if (!left || left.type !== "identifier") return;
|
|
6164
|
+
const args = right.childForFieldName("arguments");
|
|
6165
|
+
if (!args) return;
|
|
6166
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
6167
|
+
const arg = args.namedChild(i);
|
|
6168
|
+
if (arg?.type !== "keyword_argument") continue;
|
|
6169
|
+
if (arg.childForFieldName("name")?.text !== "prefix") continue;
|
|
6170
|
+
const val = arg.childForFieldName("value");
|
|
6171
|
+
const p = val ? pyStaticStringText(val) : null;
|
|
6172
|
+
if (p !== null) prefixes.set(left.text, p);
|
|
6173
|
+
}
|
|
6174
|
+
});
|
|
6175
|
+
return prefixes;
|
|
6176
|
+
}
|
|
6177
|
+
function fastapiRoutesFromSource(source, parser) {
|
|
6178
|
+
const tree = parseSource2(parser, source);
|
|
6179
|
+
const prefixes = collectApiRouterPrefixes(tree.rootNode);
|
|
6180
|
+
const out = [];
|
|
6181
|
+
walk(tree.rootNode, (node) => {
|
|
6182
|
+
if (node.type !== "decorator") return;
|
|
6183
|
+
const call = node.namedChild(0);
|
|
6184
|
+
if (!call || call.type !== "call") return;
|
|
6185
|
+
const fn = call.childForFieldName("function");
|
|
6186
|
+
if (!fn || fn.type !== "attribute") return;
|
|
6187
|
+
const method = fn.childForFieldName("attribute")?.text?.toLowerCase();
|
|
6188
|
+
if (!method) return;
|
|
6189
|
+
const isVerb = FASTAPI_METHODS.has(method);
|
|
6190
|
+
if (!isVerb && method !== "api_route") return;
|
|
6191
|
+
const args = call.childForFieldName("arguments");
|
|
6192
|
+
const first = args?.namedChild(0);
|
|
6193
|
+
if (!first || first.type !== "string") return;
|
|
6194
|
+
const rawPath = pyStaticStringText(first);
|
|
6195
|
+
if (rawPath === null || !rawPath.startsWith("/")) return;
|
|
6196
|
+
const obj = fn.childForFieldName("object")?.text;
|
|
6197
|
+
const prefix = obj ? prefixes.get(obj) ?? "" : "";
|
|
6198
|
+
const pathTemplate = canonicalizeTemplate(prefix + rawPath);
|
|
6199
|
+
const line = node.startPosition.row + 1;
|
|
6200
|
+
if (isVerb) {
|
|
6201
|
+
out.push({ method: method.toUpperCase(), pathTemplate, line, framework: "fastapi" });
|
|
6202
|
+
return;
|
|
6203
|
+
}
|
|
6204
|
+
const methods = keywordArrayStrings(args, "methods");
|
|
6205
|
+
const list = methods.length > 0 ? methods : ["ALL"];
|
|
6206
|
+
for (const m of list) {
|
|
6207
|
+
out.push({
|
|
6208
|
+
method: m === "ALL" ? "ALL" : m.toUpperCase(),
|
|
6209
|
+
pathTemplate,
|
|
6210
|
+
line,
|
|
6211
|
+
framework: "fastapi"
|
|
6212
|
+
});
|
|
6213
|
+
}
|
|
6214
|
+
});
|
|
6215
|
+
return out;
|
|
6216
|
+
}
|
|
6112
6217
|
async function addRoutes(graph, services) {
|
|
6113
6218
|
const jsParser = makeJsParser2();
|
|
6219
|
+
const pyParser = makePyParser2();
|
|
6114
6220
|
let nodesAdded = 0;
|
|
6115
6221
|
let edgesAdded = 0;
|
|
6116
6222
|
for (const service of services) {
|
|
@@ -6122,15 +6228,20 @@ async function addRoutes(graph, services) {
|
|
|
6122
6228
|
const hasFastify = deps["fastify"] !== void 0;
|
|
6123
6229
|
const hasHono = deps["hono"] !== void 0;
|
|
6124
6230
|
const hasNext = deps["next"] !== void 0;
|
|
6125
|
-
|
|
6231
|
+
const hasFastapi = deps["fastapi"] !== void 0;
|
|
6232
|
+
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasFastapi) continue;
|
|
6126
6233
|
const files = await loadSourceFiles(service.dir);
|
|
6127
6234
|
for (const file of files) {
|
|
6128
6235
|
if (isTestPath(file.path)) continue;
|
|
6129
|
-
|
|
6236
|
+
const ext = import_node_path24.default.extname(file.path);
|
|
6237
|
+
const isPy = ext === ".py";
|
|
6238
|
+
if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy) continue;
|
|
6130
6239
|
const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
|
|
6131
6240
|
let routes;
|
|
6132
6241
|
try {
|
|
6133
|
-
if (
|
|
6242
|
+
if (isPy) {
|
|
6243
|
+
routes = hasFastapi ? fastapiRoutesFromSource(file.content, pyParser) : [];
|
|
6244
|
+
} else if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
6134
6245
|
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
6135
6246
|
} else if (hasExpress || hasFastify || hasHono) {
|
|
6136
6247
|
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
|
|
@@ -6314,7 +6425,7 @@ init_cjs_shims();
|
|
|
6314
6425
|
var import_node_path26 = __toESM(require("path"), 1);
|
|
6315
6426
|
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
6316
6427
|
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
6317
|
-
var
|
|
6428
|
+
var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
|
|
6318
6429
|
var import_types14 = require("@neat.is/types");
|
|
6319
6430
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
6320
6431
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
@@ -6369,14 +6480,14 @@ function makeJsParser3() {
|
|
|
6369
6480
|
p.setLanguage(import_tree_sitter_javascript3.default);
|
|
6370
6481
|
return p;
|
|
6371
6482
|
}
|
|
6372
|
-
function
|
|
6483
|
+
function makePyParser3() {
|
|
6373
6484
|
const p = new import_tree_sitter3.default();
|
|
6374
|
-
p.setLanguage(
|
|
6485
|
+
p.setLanguage(import_tree_sitter_python3.default);
|
|
6375
6486
|
return p;
|
|
6376
6487
|
}
|
|
6377
6488
|
async function addHttpCallEdges(graph, services) {
|
|
6378
6489
|
const jsParser = makeJsParser3();
|
|
6379
|
-
const pyParser =
|
|
6490
|
+
const pyParser = makePyParser3();
|
|
6380
6491
|
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
6381
6492
|
let nodesAdded = 0;
|
|
6382
6493
|
let edgesAdded = 0;
|
|
@@ -9426,6 +9537,42 @@ function registerRoutes(scope, ctx) {
|
|
|
9426
9537
|
return reply.code(500).send({ error: err.message });
|
|
9427
9538
|
}
|
|
9428
9539
|
});
|
|
9540
|
+
scope.get("/instrumentation", async (req, reply) => {
|
|
9541
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
9542
|
+
if (!proj) return;
|
|
9543
|
+
if (!proj.scanPath) {
|
|
9544
|
+
return { engaged: null };
|
|
9545
|
+
}
|
|
9546
|
+
try {
|
|
9547
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
9548
|
+
const uninstrumented = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
9549
|
+
if (state.hookFiles.length === 0) {
|
|
9550
|
+
return {
|
|
9551
|
+
engaged: false,
|
|
9552
|
+
diagnosis: {
|
|
9553
|
+
reason: "No instrumented entry point found \u2014 NEAT hasn't written an OTel init hook for this project.",
|
|
9554
|
+
fixCommand: "neat init",
|
|
9555
|
+
detail: "Run `neat init` so NEAT writes the instrumentation hook next to your entry point, then run your app."
|
|
9556
|
+
}
|
|
9557
|
+
};
|
|
9558
|
+
}
|
|
9559
|
+
if (uninstrumented.length > 0) {
|
|
9560
|
+
const names = uninstrumented.map((u) => u.library);
|
|
9561
|
+
const more = names.length > 1 ? ` (and ${names.length - 1} more)` : "";
|
|
9562
|
+
return {
|
|
9563
|
+
engaged: false,
|
|
9564
|
+
diagnosis: {
|
|
9565
|
+
reason: `\`${names[0]}\`${more} isn't in the auto-instrumentation set, so spans from it won't reach the graph.`,
|
|
9566
|
+
fixCommand: "neat extend",
|
|
9567
|
+
detail: `Uninstrumented on your hot path: ${names.join(", ")}. Run \`neat extend\` to wire the missing instrumentation.`
|
|
9568
|
+
}
|
|
9569
|
+
};
|
|
9570
|
+
}
|
|
9571
|
+
return { engaged: true };
|
|
9572
|
+
} catch {
|
|
9573
|
+
return { engaged: null };
|
|
9574
|
+
}
|
|
9575
|
+
});
|
|
9429
9576
|
scope.post("/extend/apply", async (req, reply) => {
|
|
9430
9577
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
9431
9578
|
if (!proj) return;
|