@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/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
routeSpanToProject,
|
|
3
3
|
startDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3YUNROLT.js";
|
|
5
5
|
import {
|
|
6
6
|
ProjectNameCollisionError,
|
|
7
7
|
addProject,
|
|
@@ -37,15 +37,15 @@ import {
|
|
|
37
37
|
thresholdForEdgeType,
|
|
38
38
|
touchLastSeen,
|
|
39
39
|
writeAtomically
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-7C6ZULNU.js";
|
|
41
41
|
import {
|
|
42
42
|
startOtelGrpcReceiver
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-MVINCLQM.js";
|
|
44
44
|
import {
|
|
45
45
|
buildOtelReceiver,
|
|
46
46
|
logSpanHandler,
|
|
47
47
|
parseOtlpRequest
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-BZ3AJVAC.js";
|
|
49
49
|
export {
|
|
50
50
|
ProjectNameCollisionError,
|
|
51
51
|
addProject,
|
package/dist/neatd.cjs
CHANGED
|
@@ -342,6 +342,11 @@ function pickEnv(spanAttrs, resourceAttrs) {
|
|
|
342
342
|
}
|
|
343
343
|
return ENV_FALLBACK;
|
|
344
344
|
}
|
|
345
|
+
function normalizeDbSystem(attrs) {
|
|
346
|
+
const raw = attrs["db.system"];
|
|
347
|
+
if (typeof raw !== "string") return void 0;
|
|
348
|
+
return raw === "mongoose" ? "mongodb" : raw;
|
|
349
|
+
}
|
|
345
350
|
function messagingDestinationOf(attrs) {
|
|
346
351
|
for (const key of ["messaging.destination.name", "messaging.destination"]) {
|
|
347
352
|
const v = attrs[key];
|
|
@@ -396,7 +401,7 @@ function parseOtlpRequest(body) {
|
|
|
396
401
|
durationNanos: durationNanos(span.startTimeUnixNano, span.endTimeUnixNano),
|
|
397
402
|
env: pickEnv(attrs, resourceAttrs),
|
|
398
403
|
attributes: attrs,
|
|
399
|
-
dbSystem:
|
|
404
|
+
dbSystem: normalizeDbSystem(attrs),
|
|
400
405
|
dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
|
|
401
406
|
dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
|
|
402
407
|
messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
|
|
@@ -5300,6 +5305,7 @@ init_cjs_shims();
|
|
|
5300
5305
|
var import_node_path22 = __toESM(require("path"), 1);
|
|
5301
5306
|
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
5302
5307
|
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5308
|
+
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
5303
5309
|
var import_types11 = require("@neat.is/types");
|
|
5304
5310
|
var PARSE_CHUNK2 = 16384;
|
|
5305
5311
|
function parseSource2(parser, source) {
|
|
@@ -5312,6 +5318,11 @@ function makeJsParser2() {
|
|
|
5312
5318
|
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
5313
5319
|
return p;
|
|
5314
5320
|
}
|
|
5321
|
+
function makePyParser2() {
|
|
5322
|
+
const p = new import_tree_sitter2.default();
|
|
5323
|
+
p.setLanguage(import_tree_sitter_python2.default);
|
|
5324
|
+
return p;
|
|
5325
|
+
}
|
|
5315
5326
|
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
5316
5327
|
"get",
|
|
5317
5328
|
"post",
|
|
@@ -5324,6 +5335,7 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
|
5324
5335
|
]);
|
|
5325
5336
|
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
5326
5337
|
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5338
|
+
var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
|
|
5327
5339
|
function canonicalizeTemplate(raw) {
|
|
5328
5340
|
let p = raw.split("?")[0].split("#")[0];
|
|
5329
5341
|
if (!p.startsWith("/")) p = "/" + p;
|
|
@@ -5551,8 +5563,102 @@ function nextRoutesFromFile(source, relFile, parser) {
|
|
|
5551
5563
|
}
|
|
5552
5564
|
return [];
|
|
5553
5565
|
}
|
|
5566
|
+
function pyStaticStringText(node) {
|
|
5567
|
+
if (node.type !== "string") return null;
|
|
5568
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5569
|
+
const child = node.namedChild(i);
|
|
5570
|
+
if (child?.type === "interpolation") return null;
|
|
5571
|
+
if (child?.type === "string_content") return child.text;
|
|
5572
|
+
}
|
|
5573
|
+
return "";
|
|
5574
|
+
}
|
|
5575
|
+
function keywordArrayStrings(argsNode, key) {
|
|
5576
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
5577
|
+
const arg = argsNode.namedChild(i);
|
|
5578
|
+
if (arg?.type !== "keyword_argument") continue;
|
|
5579
|
+
if (arg.childForFieldName("name")?.text !== key) continue;
|
|
5580
|
+
const val = arg.childForFieldName("value");
|
|
5581
|
+
if (!val || val.type !== "list") return [];
|
|
5582
|
+
const out = [];
|
|
5583
|
+
for (let j = 0; j < val.namedChildCount; j++) {
|
|
5584
|
+
const el = val.namedChild(j);
|
|
5585
|
+
if (el?.type === "string") {
|
|
5586
|
+
const s = pyStaticStringText(el);
|
|
5587
|
+
if (s) out.push(s);
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
5590
|
+
return out;
|
|
5591
|
+
}
|
|
5592
|
+
return [];
|
|
5593
|
+
}
|
|
5594
|
+
function collectApiRouterPrefixes(root) {
|
|
5595
|
+
const prefixes = /* @__PURE__ */ new Map();
|
|
5596
|
+
walk(root, (node) => {
|
|
5597
|
+
if (node.type !== "assignment") return;
|
|
5598
|
+
const right = node.childForFieldName("right");
|
|
5599
|
+
if (!right || right.type !== "call") return;
|
|
5600
|
+
const fn = right.childForFieldName("function");
|
|
5601
|
+
if (!fn) return;
|
|
5602
|
+
const ctor = fn.type === "attribute" ? fn.childForFieldName("attribute")?.text : fn.text;
|
|
5603
|
+
if (ctor !== "APIRouter") return;
|
|
5604
|
+
const left = node.childForFieldName("left");
|
|
5605
|
+
if (!left || left.type !== "identifier") return;
|
|
5606
|
+
const args = right.childForFieldName("arguments");
|
|
5607
|
+
if (!args) return;
|
|
5608
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
5609
|
+
const arg = args.namedChild(i);
|
|
5610
|
+
if (arg?.type !== "keyword_argument") continue;
|
|
5611
|
+
if (arg.childForFieldName("name")?.text !== "prefix") continue;
|
|
5612
|
+
const val = arg.childForFieldName("value");
|
|
5613
|
+
const p = val ? pyStaticStringText(val) : null;
|
|
5614
|
+
if (p !== null) prefixes.set(left.text, p);
|
|
5615
|
+
}
|
|
5616
|
+
});
|
|
5617
|
+
return prefixes;
|
|
5618
|
+
}
|
|
5619
|
+
function fastapiRoutesFromSource(source, parser) {
|
|
5620
|
+
const tree = parseSource2(parser, source);
|
|
5621
|
+
const prefixes = collectApiRouterPrefixes(tree.rootNode);
|
|
5622
|
+
const out = [];
|
|
5623
|
+
walk(tree.rootNode, (node) => {
|
|
5624
|
+
if (node.type !== "decorator") return;
|
|
5625
|
+
const call = node.namedChild(0);
|
|
5626
|
+
if (!call || call.type !== "call") return;
|
|
5627
|
+
const fn = call.childForFieldName("function");
|
|
5628
|
+
if (!fn || fn.type !== "attribute") return;
|
|
5629
|
+
const method = fn.childForFieldName("attribute")?.text?.toLowerCase();
|
|
5630
|
+
if (!method) return;
|
|
5631
|
+
const isVerb = FASTAPI_METHODS.has(method);
|
|
5632
|
+
if (!isVerb && method !== "api_route") return;
|
|
5633
|
+
const args = call.childForFieldName("arguments");
|
|
5634
|
+
const first = args?.namedChild(0);
|
|
5635
|
+
if (!first || first.type !== "string") return;
|
|
5636
|
+
const rawPath = pyStaticStringText(first);
|
|
5637
|
+
if (rawPath === null || !rawPath.startsWith("/")) return;
|
|
5638
|
+
const obj = fn.childForFieldName("object")?.text;
|
|
5639
|
+
const prefix = obj ? prefixes.get(obj) ?? "" : "";
|
|
5640
|
+
const pathTemplate = canonicalizeTemplate(prefix + rawPath);
|
|
5641
|
+
const line = node.startPosition.row + 1;
|
|
5642
|
+
if (isVerb) {
|
|
5643
|
+
out.push({ method: method.toUpperCase(), pathTemplate, line, framework: "fastapi" });
|
|
5644
|
+
return;
|
|
5645
|
+
}
|
|
5646
|
+
const methods = keywordArrayStrings(args, "methods");
|
|
5647
|
+
const list = methods.length > 0 ? methods : ["ALL"];
|
|
5648
|
+
for (const m of list) {
|
|
5649
|
+
out.push({
|
|
5650
|
+
method: m === "ALL" ? "ALL" : m.toUpperCase(),
|
|
5651
|
+
pathTemplate,
|
|
5652
|
+
line,
|
|
5653
|
+
framework: "fastapi"
|
|
5654
|
+
});
|
|
5655
|
+
}
|
|
5656
|
+
});
|
|
5657
|
+
return out;
|
|
5658
|
+
}
|
|
5554
5659
|
async function addRoutes(graph, services) {
|
|
5555
5660
|
const jsParser = makeJsParser2();
|
|
5661
|
+
const pyParser = makePyParser2();
|
|
5556
5662
|
let nodesAdded = 0;
|
|
5557
5663
|
let edgesAdded = 0;
|
|
5558
5664
|
for (const service of services) {
|
|
@@ -5564,15 +5670,20 @@ async function addRoutes(graph, services) {
|
|
|
5564
5670
|
const hasFastify = deps["fastify"] !== void 0;
|
|
5565
5671
|
const hasHono = deps["hono"] !== void 0;
|
|
5566
5672
|
const hasNext = deps["next"] !== void 0;
|
|
5567
|
-
|
|
5673
|
+
const hasFastapi = deps["fastapi"] !== void 0;
|
|
5674
|
+
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasFastapi) continue;
|
|
5568
5675
|
const files = await loadSourceFiles(service.dir);
|
|
5569
5676
|
for (const file of files) {
|
|
5570
5677
|
if (isTestPath(file.path)) continue;
|
|
5571
|
-
|
|
5678
|
+
const ext = import_node_path22.default.extname(file.path);
|
|
5679
|
+
const isPy = ext === ".py";
|
|
5680
|
+
if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy) continue;
|
|
5572
5681
|
const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
|
|
5573
5682
|
let routes;
|
|
5574
5683
|
try {
|
|
5575
|
-
if (
|
|
5684
|
+
if (isPy) {
|
|
5685
|
+
routes = hasFastapi ? fastapiRoutesFromSource(file.content, pyParser) : [];
|
|
5686
|
+
} else if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
5576
5687
|
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
5577
5688
|
} else if (hasExpress || hasFastify || hasHono) {
|
|
5578
5689
|
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
|
|
@@ -5756,7 +5867,7 @@ init_cjs_shims();
|
|
|
5756
5867
|
var import_node_path24 = __toESM(require("path"), 1);
|
|
5757
5868
|
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
5758
5869
|
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5759
|
-
var
|
|
5870
|
+
var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
|
|
5760
5871
|
var import_types13 = require("@neat.is/types");
|
|
5761
5872
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
5762
5873
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
@@ -5811,14 +5922,14 @@ function makeJsParser3() {
|
|
|
5811
5922
|
p.setLanguage(import_tree_sitter_javascript3.default);
|
|
5812
5923
|
return p;
|
|
5813
5924
|
}
|
|
5814
|
-
function
|
|
5925
|
+
function makePyParser3() {
|
|
5815
5926
|
const p = new import_tree_sitter3.default();
|
|
5816
|
-
p.setLanguage(
|
|
5927
|
+
p.setLanguage(import_tree_sitter_python3.default);
|
|
5817
5928
|
return p;
|
|
5818
5929
|
}
|
|
5819
5930
|
async function addHttpCallEdges(graph, services) {
|
|
5820
5931
|
const jsParser = makeJsParser3();
|
|
5821
|
-
const pyParser =
|
|
5932
|
+
const pyParser = makePyParser3();
|
|
5822
5933
|
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
5823
5934
|
let nodesAdded = 0;
|
|
5824
5935
|
let edgesAdded = 0;
|
|
@@ -9774,6 +9885,42 @@ function registerRoutes(scope, ctx) {
|
|
|
9774
9885
|
return reply.code(500).send({ error: err.message });
|
|
9775
9886
|
}
|
|
9776
9887
|
});
|
|
9888
|
+
scope.get("/instrumentation", async (req2, reply) => {
|
|
9889
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
9890
|
+
if (!proj) return;
|
|
9891
|
+
if (!proj.scanPath) {
|
|
9892
|
+
return { engaged: null };
|
|
9893
|
+
}
|
|
9894
|
+
try {
|
|
9895
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
9896
|
+
const uninstrumented = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
9897
|
+
if (state.hookFiles.length === 0) {
|
|
9898
|
+
return {
|
|
9899
|
+
engaged: false,
|
|
9900
|
+
diagnosis: {
|
|
9901
|
+
reason: "No instrumented entry point found \u2014 NEAT hasn't written an OTel init hook for this project.",
|
|
9902
|
+
fixCommand: "neat init",
|
|
9903
|
+
detail: "Run `neat init` so NEAT writes the instrumentation hook next to your entry point, then run your app."
|
|
9904
|
+
}
|
|
9905
|
+
};
|
|
9906
|
+
}
|
|
9907
|
+
if (uninstrumented.length > 0) {
|
|
9908
|
+
const names = uninstrumented.map((u) => u.library);
|
|
9909
|
+
const more = names.length > 1 ? ` (and ${names.length - 1} more)` : "";
|
|
9910
|
+
return {
|
|
9911
|
+
engaged: false,
|
|
9912
|
+
diagnosis: {
|
|
9913
|
+
reason: `\`${names[0]}\`${more} isn't in the auto-instrumentation set, so spans from it won't reach the graph.`,
|
|
9914
|
+
fixCommand: "neat extend",
|
|
9915
|
+
detail: `Uninstrumented on your hot path: ${names.join(", ")}. Run \`neat extend\` to wire the missing instrumentation.`
|
|
9916
|
+
}
|
|
9917
|
+
};
|
|
9918
|
+
}
|
|
9919
|
+
return { engaged: true };
|
|
9920
|
+
} catch {
|
|
9921
|
+
return { engaged: null };
|
|
9922
|
+
}
|
|
9923
|
+
});
|
|
9777
9924
|
scope.post("/extend/apply", async (req2, reply) => {
|
|
9778
9925
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
9779
9926
|
if (!proj) return;
|
|
@@ -11636,15 +11783,21 @@ var PROVIDER_DISPATCH = {
|
|
|
11636
11783
|
resolveTarget: createCloudflareResolveTarget(config, graph)
|
|
11637
11784
|
};
|
|
11638
11785
|
},
|
|
11639
|
-
// GET /
|
|
11640
|
-
//
|
|
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`.
|
|
11641
11794
|
validate({ credentials, options, fetchImpl }) {
|
|
11642
11795
|
const cfg = options;
|
|
11643
11796
|
const baseUrl = cfg.baseUrl ?? CLOUDFLARE_API_BASE_URL;
|
|
11644
11797
|
return authProbe({
|
|
11645
11798
|
provider: "cloudflare",
|
|
11646
11799
|
accountKey: cfg.accountId ?? "validate",
|
|
11647
|
-
url: `${baseUrl}/
|
|
11800
|
+
url: `${baseUrl}/accounts/${cfg.accountId ?? ""}/tokens/verify`,
|
|
11648
11801
|
token: String(credentials.apiToken ?? ""),
|
|
11649
11802
|
...fetchImpl ? { fetchImpl } : {}
|
|
11650
11803
|
});
|