@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.cjs
CHANGED
|
@@ -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];
|
|
@@ -395,7 +400,7 @@ 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,
|
|
400
405
|
dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
|
|
401
406
|
messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
|
|
@@ -5340,6 +5345,7 @@ init_cjs_shims();
|
|
|
5340
5345
|
var import_node_path22 = __toESM(require("path"), 1);
|
|
5341
5346
|
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
5342
5347
|
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5348
|
+
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
5343
5349
|
var import_types11 = require("@neat.is/types");
|
|
5344
5350
|
var PARSE_CHUNK2 = 16384;
|
|
5345
5351
|
function parseSource2(parser, source) {
|
|
@@ -5352,6 +5358,11 @@ function makeJsParser2() {
|
|
|
5352
5358
|
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
5353
5359
|
return p;
|
|
5354
5360
|
}
|
|
5361
|
+
function makePyParser2() {
|
|
5362
|
+
const p = new import_tree_sitter2.default();
|
|
5363
|
+
p.setLanguage(import_tree_sitter_python2.default);
|
|
5364
|
+
return p;
|
|
5365
|
+
}
|
|
5355
5366
|
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
5356
5367
|
"get",
|
|
5357
5368
|
"post",
|
|
@@ -5364,6 +5375,7 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
|
5364
5375
|
]);
|
|
5365
5376
|
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
5366
5377
|
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5378
|
+
var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
|
|
5367
5379
|
function canonicalizeTemplate(raw) {
|
|
5368
5380
|
let p = raw.split("?")[0].split("#")[0];
|
|
5369
5381
|
if (!p.startsWith("/")) p = "/" + p;
|
|
@@ -5591,8 +5603,102 @@ function nextRoutesFromFile(source, relFile, parser) {
|
|
|
5591
5603
|
}
|
|
5592
5604
|
return [];
|
|
5593
5605
|
}
|
|
5606
|
+
function pyStaticStringText(node) {
|
|
5607
|
+
if (node.type !== "string") return null;
|
|
5608
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5609
|
+
const child = node.namedChild(i);
|
|
5610
|
+
if (child?.type === "interpolation") return null;
|
|
5611
|
+
if (child?.type === "string_content") return child.text;
|
|
5612
|
+
}
|
|
5613
|
+
return "";
|
|
5614
|
+
}
|
|
5615
|
+
function keywordArrayStrings(argsNode, key) {
|
|
5616
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
5617
|
+
const arg = argsNode.namedChild(i);
|
|
5618
|
+
if (arg?.type !== "keyword_argument") continue;
|
|
5619
|
+
if (arg.childForFieldName("name")?.text !== key) continue;
|
|
5620
|
+
const val = arg.childForFieldName("value");
|
|
5621
|
+
if (!val || val.type !== "list") return [];
|
|
5622
|
+
const out = [];
|
|
5623
|
+
for (let j = 0; j < val.namedChildCount; j++) {
|
|
5624
|
+
const el = val.namedChild(j);
|
|
5625
|
+
if (el?.type === "string") {
|
|
5626
|
+
const s = pyStaticStringText(el);
|
|
5627
|
+
if (s) out.push(s);
|
|
5628
|
+
}
|
|
5629
|
+
}
|
|
5630
|
+
return out;
|
|
5631
|
+
}
|
|
5632
|
+
return [];
|
|
5633
|
+
}
|
|
5634
|
+
function collectApiRouterPrefixes(root) {
|
|
5635
|
+
const prefixes = /* @__PURE__ */ new Map();
|
|
5636
|
+
walk(root, (node) => {
|
|
5637
|
+
if (node.type !== "assignment") return;
|
|
5638
|
+
const right = node.childForFieldName("right");
|
|
5639
|
+
if (!right || right.type !== "call") return;
|
|
5640
|
+
const fn = right.childForFieldName("function");
|
|
5641
|
+
if (!fn) return;
|
|
5642
|
+
const ctor = fn.type === "attribute" ? fn.childForFieldName("attribute")?.text : fn.text;
|
|
5643
|
+
if (ctor !== "APIRouter") return;
|
|
5644
|
+
const left = node.childForFieldName("left");
|
|
5645
|
+
if (!left || left.type !== "identifier") return;
|
|
5646
|
+
const args = right.childForFieldName("arguments");
|
|
5647
|
+
if (!args) return;
|
|
5648
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
5649
|
+
const arg = args.namedChild(i);
|
|
5650
|
+
if (arg?.type !== "keyword_argument") continue;
|
|
5651
|
+
if (arg.childForFieldName("name")?.text !== "prefix") continue;
|
|
5652
|
+
const val = arg.childForFieldName("value");
|
|
5653
|
+
const p = val ? pyStaticStringText(val) : null;
|
|
5654
|
+
if (p !== null) prefixes.set(left.text, p);
|
|
5655
|
+
}
|
|
5656
|
+
});
|
|
5657
|
+
return prefixes;
|
|
5658
|
+
}
|
|
5659
|
+
function fastapiRoutesFromSource(source, parser) {
|
|
5660
|
+
const tree = parseSource2(parser, source);
|
|
5661
|
+
const prefixes = collectApiRouterPrefixes(tree.rootNode);
|
|
5662
|
+
const out = [];
|
|
5663
|
+
walk(tree.rootNode, (node) => {
|
|
5664
|
+
if (node.type !== "decorator") return;
|
|
5665
|
+
const call = node.namedChild(0);
|
|
5666
|
+
if (!call || call.type !== "call") return;
|
|
5667
|
+
const fn = call.childForFieldName("function");
|
|
5668
|
+
if (!fn || fn.type !== "attribute") return;
|
|
5669
|
+
const method = fn.childForFieldName("attribute")?.text?.toLowerCase();
|
|
5670
|
+
if (!method) return;
|
|
5671
|
+
const isVerb = FASTAPI_METHODS.has(method);
|
|
5672
|
+
if (!isVerb && method !== "api_route") return;
|
|
5673
|
+
const args = call.childForFieldName("arguments");
|
|
5674
|
+
const first = args?.namedChild(0);
|
|
5675
|
+
if (!first || first.type !== "string") return;
|
|
5676
|
+
const rawPath = pyStaticStringText(first);
|
|
5677
|
+
if (rawPath === null || !rawPath.startsWith("/")) return;
|
|
5678
|
+
const obj = fn.childForFieldName("object")?.text;
|
|
5679
|
+
const prefix = obj ? prefixes.get(obj) ?? "" : "";
|
|
5680
|
+
const pathTemplate = canonicalizeTemplate(prefix + rawPath);
|
|
5681
|
+
const line = node.startPosition.row + 1;
|
|
5682
|
+
if (isVerb) {
|
|
5683
|
+
out.push({ method: method.toUpperCase(), pathTemplate, line, framework: "fastapi" });
|
|
5684
|
+
return;
|
|
5685
|
+
}
|
|
5686
|
+
const methods = keywordArrayStrings(args, "methods");
|
|
5687
|
+
const list = methods.length > 0 ? methods : ["ALL"];
|
|
5688
|
+
for (const m of list) {
|
|
5689
|
+
out.push({
|
|
5690
|
+
method: m === "ALL" ? "ALL" : m.toUpperCase(),
|
|
5691
|
+
pathTemplate,
|
|
5692
|
+
line,
|
|
5693
|
+
framework: "fastapi"
|
|
5694
|
+
});
|
|
5695
|
+
}
|
|
5696
|
+
});
|
|
5697
|
+
return out;
|
|
5698
|
+
}
|
|
5594
5699
|
async function addRoutes(graph, services) {
|
|
5595
5700
|
const jsParser = makeJsParser2();
|
|
5701
|
+
const pyParser = makePyParser2();
|
|
5596
5702
|
let nodesAdded = 0;
|
|
5597
5703
|
let edgesAdded = 0;
|
|
5598
5704
|
for (const service of services) {
|
|
@@ -5604,15 +5710,20 @@ async function addRoutes(graph, services) {
|
|
|
5604
5710
|
const hasFastify = deps["fastify"] !== void 0;
|
|
5605
5711
|
const hasHono = deps["hono"] !== void 0;
|
|
5606
5712
|
const hasNext = deps["next"] !== void 0;
|
|
5607
|
-
|
|
5713
|
+
const hasFastapi = deps["fastapi"] !== void 0;
|
|
5714
|
+
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasFastapi) continue;
|
|
5608
5715
|
const files = await loadSourceFiles(service.dir);
|
|
5609
5716
|
for (const file of files) {
|
|
5610
5717
|
if (isTestPath(file.path)) continue;
|
|
5611
|
-
|
|
5718
|
+
const ext = import_node_path22.default.extname(file.path);
|
|
5719
|
+
const isPy = ext === ".py";
|
|
5720
|
+
if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy) continue;
|
|
5612
5721
|
const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
|
|
5613
5722
|
let routes;
|
|
5614
5723
|
try {
|
|
5615
|
-
if (
|
|
5724
|
+
if (isPy) {
|
|
5725
|
+
routes = hasFastapi ? fastapiRoutesFromSource(file.content, pyParser) : [];
|
|
5726
|
+
} else if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
5616
5727
|
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
5617
5728
|
} else if (hasExpress || hasFastify || hasHono) {
|
|
5618
5729
|
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
|
|
@@ -5796,7 +5907,7 @@ init_cjs_shims();
|
|
|
5796
5907
|
var import_node_path24 = __toESM(require("path"), 1);
|
|
5797
5908
|
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
5798
5909
|
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5799
|
-
var
|
|
5910
|
+
var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
|
|
5800
5911
|
var import_types13 = require("@neat.is/types");
|
|
5801
5912
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
5802
5913
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
@@ -5851,14 +5962,14 @@ function makeJsParser3() {
|
|
|
5851
5962
|
p.setLanguage(import_tree_sitter_javascript3.default);
|
|
5852
5963
|
return p;
|
|
5853
5964
|
}
|
|
5854
|
-
function
|
|
5965
|
+
function makePyParser3() {
|
|
5855
5966
|
const p = new import_tree_sitter3.default();
|
|
5856
|
-
p.setLanguage(
|
|
5967
|
+
p.setLanguage(import_tree_sitter_python3.default);
|
|
5857
5968
|
return p;
|
|
5858
5969
|
}
|
|
5859
5970
|
async function addHttpCallEdges(graph, services) {
|
|
5860
5971
|
const jsParser = makeJsParser3();
|
|
5861
|
-
const pyParser =
|
|
5972
|
+
const pyParser = makePyParser3();
|
|
5862
5973
|
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
5863
5974
|
let nodesAdded = 0;
|
|
5864
5975
|
let edgesAdded = 0;
|
|
@@ -9872,6 +9983,42 @@ function registerRoutes(scope, ctx) {
|
|
|
9872
9983
|
return reply.code(500).send({ error: err.message });
|
|
9873
9984
|
}
|
|
9874
9985
|
});
|
|
9986
|
+
scope.get("/instrumentation", async (req, reply) => {
|
|
9987
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
9988
|
+
if (!proj) return;
|
|
9989
|
+
if (!proj.scanPath) {
|
|
9990
|
+
return { engaged: null };
|
|
9991
|
+
}
|
|
9992
|
+
try {
|
|
9993
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
9994
|
+
const uninstrumented = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
9995
|
+
if (state.hookFiles.length === 0) {
|
|
9996
|
+
return {
|
|
9997
|
+
engaged: false,
|
|
9998
|
+
diagnosis: {
|
|
9999
|
+
reason: "No instrumented entry point found \u2014 NEAT hasn't written an OTel init hook for this project.",
|
|
10000
|
+
fixCommand: "neat init",
|
|
10001
|
+
detail: "Run `neat init` so NEAT writes the instrumentation hook next to your entry point, then run your app."
|
|
10002
|
+
}
|
|
10003
|
+
};
|
|
10004
|
+
}
|
|
10005
|
+
if (uninstrumented.length > 0) {
|
|
10006
|
+
const names = uninstrumented.map((u) => u.library);
|
|
10007
|
+
const more = names.length > 1 ? ` (and ${names.length - 1} more)` : "";
|
|
10008
|
+
return {
|
|
10009
|
+
engaged: false,
|
|
10010
|
+
diagnosis: {
|
|
10011
|
+
reason: `\`${names[0]}\`${more} isn't in the auto-instrumentation set, so spans from it won't reach the graph.`,
|
|
10012
|
+
fixCommand: "neat extend",
|
|
10013
|
+
detail: `Uninstrumented on your hot path: ${names.join(", ")}. Run \`neat extend\` to wire the missing instrumentation.`
|
|
10014
|
+
}
|
|
10015
|
+
};
|
|
10016
|
+
}
|
|
10017
|
+
return { engaged: true };
|
|
10018
|
+
} catch {
|
|
10019
|
+
return { engaged: null };
|
|
10020
|
+
}
|
|
10021
|
+
});
|
|
9875
10022
|
scope.post("/extend/apply", async (req, reply) => {
|
|
9876
10023
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
9877
10024
|
if (!proj) return;
|
|
@@ -11742,15 +11889,21 @@ var PROVIDER_DISPATCH = {
|
|
|
11742
11889
|
resolveTarget: createCloudflareResolveTarget(config, graph)
|
|
11743
11890
|
};
|
|
11744
11891
|
},
|
|
11745
|
-
// GET /
|
|
11746
|
-
//
|
|
11892
|
+
// GET /accounts/{accountId}/tokens/verify — the *account-scoped* token-verify
|
|
11893
|
+
// endpoint. A Workers connector token is scoped to the account, and the
|
|
11894
|
+
// user-level `GET /user/tokens/verify` returns 401 "Invalid API Token" for
|
|
11895
|
+
// such a token even though it authenticates fine against the account's own
|
|
11896
|
+
// resources (confirmed live). Probing the account-scoped verify endpoint —
|
|
11897
|
+
// `accountId` is already required for this provider — returns 200
|
|
11898
|
+
// `{status:"active"}` for a working token and 401 for a bad one, so a valid
|
|
11899
|
+
// Workers token is no longer falsely rejected at `neat connector add`.
|
|
11747
11900
|
validate({ credentials, options, fetchImpl }) {
|
|
11748
11901
|
const cfg = options;
|
|
11749
11902
|
const baseUrl = cfg.baseUrl ?? CLOUDFLARE_API_BASE_URL;
|
|
11750
11903
|
return authProbe({
|
|
11751
11904
|
provider: "cloudflare",
|
|
11752
11905
|
accountKey: cfg.accountId ?? "validate",
|
|
11753
|
-
url: `${baseUrl}/
|
|
11906
|
+
url: `${baseUrl}/accounts/${cfg.accountId ?? ""}/tokens/verify`,
|
|
11754
11907
|
token: String(credentials.apiToken ?? ""),
|
|
11755
11908
|
...fetchImpl ? { fetchImpl } : {}
|
|
11756
11909
|
});
|