@neat.is/core 0.6.0 → 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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  routeSpanToProject,
3
3
  startDaemon
4
- } from "./chunk-5PVQJLPR.js";
4
+ } from "./chunk-3YUNROLT.js";
5
5
  import {
6
6
  ProjectNameCollisionError,
7
7
  addProject,
@@ -37,7 +37,7 @@ import {
37
37
  thresholdForEdgeType,
38
38
  touchLastSeen,
39
39
  writeAtomically
40
- } from "./chunk-CS4GHQO3.js";
40
+ } from "./chunk-7C6ZULNU.js";
41
41
  import {
42
42
  startOtelGrpcReceiver
43
43
  } from "./chunk-MVINCLQM.js";
package/dist/neatd.cjs CHANGED
@@ -5305,6 +5305,7 @@ init_cjs_shims();
5305
5305
  var import_node_path22 = __toESM(require("path"), 1);
5306
5306
  var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
5307
5307
  var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5308
+ var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5308
5309
  var import_types11 = require("@neat.is/types");
5309
5310
  var PARSE_CHUNK2 = 16384;
5310
5311
  function parseSource2(parser, source) {
@@ -5317,6 +5318,11 @@ function makeJsParser2() {
5317
5318
  p.setLanguage(import_tree_sitter_javascript2.default);
5318
5319
  return p;
5319
5320
  }
5321
+ function makePyParser2() {
5322
+ const p = new import_tree_sitter2.default();
5323
+ p.setLanguage(import_tree_sitter_python2.default);
5324
+ return p;
5325
+ }
5320
5326
  var ROUTER_METHODS = /* @__PURE__ */ new Set([
5321
5327
  "get",
5322
5328
  "post",
@@ -5329,6 +5335,7 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
5329
5335
  ]);
5330
5336
  var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
5331
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"]);
5332
5339
  function canonicalizeTemplate(raw) {
5333
5340
  let p = raw.split("?")[0].split("#")[0];
5334
5341
  if (!p.startsWith("/")) p = "/" + p;
@@ -5556,8 +5563,102 @@ function nextRoutesFromFile(source, relFile, parser) {
5556
5563
  }
5557
5564
  return [];
5558
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
+ }
5559
5659
  async function addRoutes(graph, services) {
5560
5660
  const jsParser = makeJsParser2();
5661
+ const pyParser = makePyParser2();
5561
5662
  let nodesAdded = 0;
5562
5663
  let edgesAdded = 0;
5563
5664
  for (const service of services) {
@@ -5569,15 +5670,20 @@ async function addRoutes(graph, services) {
5569
5670
  const hasFastify = deps["fastify"] !== void 0;
5570
5671
  const hasHono = deps["hono"] !== void 0;
5571
5672
  const hasNext = deps["next"] !== void 0;
5572
- if (!hasExpress && !hasFastify && !hasHono && !hasNext) continue;
5673
+ const hasFastapi = deps["fastapi"] !== void 0;
5674
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasFastapi) continue;
5573
5675
  const files = await loadSourceFiles(service.dir);
5574
5676
  for (const file of files) {
5575
5677
  if (isTestPath(file.path)) continue;
5576
- if (!JS_ROUTE_EXTENSIONS.has(import_node_path22.default.extname(file.path))) continue;
5678
+ const ext = import_node_path22.default.extname(file.path);
5679
+ const isPy = ext === ".py";
5680
+ if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy) continue;
5577
5681
  const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
5578
5682
  let routes;
5579
5683
  try {
5580
- if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5684
+ if (isPy) {
5685
+ routes = hasFastapi ? fastapiRoutesFromSource(file.content, pyParser) : [];
5686
+ } else if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5581
5687
  routes = nextRoutesFromFile(file.content, relFile, jsParser);
5582
5688
  } else if (hasExpress || hasFastify || hasHono) {
5583
5689
  routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
@@ -5761,7 +5867,7 @@ init_cjs_shims();
5761
5867
  var import_node_path24 = __toESM(require("path"), 1);
5762
5868
  var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5763
5869
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
5764
- var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5870
+ var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
5765
5871
  var import_types13 = require("@neat.is/types");
5766
5872
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
5767
5873
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
@@ -5816,14 +5922,14 @@ function makeJsParser3() {
5816
5922
  p.setLanguage(import_tree_sitter_javascript3.default);
5817
5923
  return p;
5818
5924
  }
5819
- function makePyParser2() {
5925
+ function makePyParser3() {
5820
5926
  const p = new import_tree_sitter3.default();
5821
- p.setLanguage(import_tree_sitter_python2.default);
5927
+ p.setLanguage(import_tree_sitter_python3.default);
5822
5928
  return p;
5823
5929
  }
5824
5930
  async function addHttpCallEdges(graph, services) {
5825
5931
  const jsParser = makeJsParser3();
5826
- const pyParser = makePyParser2();
5932
+ const pyParser = makePyParser3();
5827
5933
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
5828
5934
  let nodesAdded = 0;
5829
5935
  let edgesAdded = 0;