@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/cli.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  resolveNeatVersion,
13
13
  validateConnectorEntry,
14
14
  writeDaemonRecord
15
- } from "./chunk-5PVQJLPR.js";
15
+ } from "./chunk-3YUNROLT.js";
16
16
  import {
17
17
  buildSearchIndex
18
18
  } from "./chunk-BIY46Q6U.js";
@@ -71,7 +71,7 @@ import {
71
71
  startPersistLoop,
72
72
  startStalenessLoop,
73
73
  upsertConnectorEntry
74
- } from "./chunk-CS4GHQO3.js";
74
+ } from "./chunk-7C6ZULNU.js";
75
75
  import {
76
76
  startOtelGrpcReceiver
77
77
  } from "./chunk-MVINCLQM.js";
package/dist/index.cjs CHANGED
@@ -5345,6 +5345,7 @@ init_cjs_shims();
5345
5345
  var import_node_path22 = __toESM(require("path"), 1);
5346
5346
  var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
5347
5347
  var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5348
+ var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5348
5349
  var import_types11 = require("@neat.is/types");
5349
5350
  var PARSE_CHUNK2 = 16384;
5350
5351
  function parseSource2(parser, source) {
@@ -5357,6 +5358,11 @@ function makeJsParser2() {
5357
5358
  p.setLanguage(import_tree_sitter_javascript2.default);
5358
5359
  return p;
5359
5360
  }
5361
+ function makePyParser2() {
5362
+ const p = new import_tree_sitter2.default();
5363
+ p.setLanguage(import_tree_sitter_python2.default);
5364
+ return p;
5365
+ }
5360
5366
  var ROUTER_METHODS = /* @__PURE__ */ new Set([
5361
5367
  "get",
5362
5368
  "post",
@@ -5369,6 +5375,7 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
5369
5375
  ]);
5370
5376
  var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
5371
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"]);
5372
5379
  function canonicalizeTemplate(raw) {
5373
5380
  let p = raw.split("?")[0].split("#")[0];
5374
5381
  if (!p.startsWith("/")) p = "/" + p;
@@ -5596,8 +5603,102 @@ function nextRoutesFromFile(source, relFile, parser) {
5596
5603
  }
5597
5604
  return [];
5598
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
+ }
5599
5699
  async function addRoutes(graph, services) {
5600
5700
  const jsParser = makeJsParser2();
5701
+ const pyParser = makePyParser2();
5601
5702
  let nodesAdded = 0;
5602
5703
  let edgesAdded = 0;
5603
5704
  for (const service of services) {
@@ -5609,15 +5710,20 @@ async function addRoutes(graph, services) {
5609
5710
  const hasFastify = deps["fastify"] !== void 0;
5610
5711
  const hasHono = deps["hono"] !== void 0;
5611
5712
  const hasNext = deps["next"] !== void 0;
5612
- if (!hasExpress && !hasFastify && !hasHono && !hasNext) continue;
5713
+ const hasFastapi = deps["fastapi"] !== void 0;
5714
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasFastapi) continue;
5613
5715
  const files = await loadSourceFiles(service.dir);
5614
5716
  for (const file of files) {
5615
5717
  if (isTestPath(file.path)) continue;
5616
- if (!JS_ROUTE_EXTENSIONS.has(import_node_path22.default.extname(file.path))) continue;
5718
+ const ext = import_node_path22.default.extname(file.path);
5719
+ const isPy = ext === ".py";
5720
+ if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy) continue;
5617
5721
  const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
5618
5722
  let routes;
5619
5723
  try {
5620
- if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5724
+ if (isPy) {
5725
+ routes = hasFastapi ? fastapiRoutesFromSource(file.content, pyParser) : [];
5726
+ } else if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5621
5727
  routes = nextRoutesFromFile(file.content, relFile, jsParser);
5622
5728
  } else if (hasExpress || hasFastify || hasHono) {
5623
5729
  routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
@@ -5801,7 +5907,7 @@ init_cjs_shims();
5801
5907
  var import_node_path24 = __toESM(require("path"), 1);
5802
5908
  var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5803
5909
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
5804
- var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5910
+ var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
5805
5911
  var import_types13 = require("@neat.is/types");
5806
5912
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
5807
5913
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
@@ -5856,14 +5962,14 @@ function makeJsParser3() {
5856
5962
  p.setLanguage(import_tree_sitter_javascript3.default);
5857
5963
  return p;
5858
5964
  }
5859
- function makePyParser2() {
5965
+ function makePyParser3() {
5860
5966
  const p = new import_tree_sitter3.default();
5861
- p.setLanguage(import_tree_sitter_python2.default);
5967
+ p.setLanguage(import_tree_sitter_python3.default);
5862
5968
  return p;
5863
5969
  }
5864
5970
  async function addHttpCallEdges(graph, services) {
5865
5971
  const jsParser = makeJsParser3();
5866
- const pyParser = makePyParser2();
5972
+ const pyParser = makePyParser3();
5867
5973
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
5868
5974
  let nodesAdded = 0;
5869
5975
  let edgesAdded = 0;