@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.cjs CHANGED
@@ -5363,6 +5363,7 @@ init_cjs_shims();
5363
5363
  var import_node_path23 = __toESM(require("path"), 1);
5364
5364
  var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
5365
5365
  var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5366
+ var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5366
5367
  var import_types11 = require("@neat.is/types");
5367
5368
  var PARSE_CHUNK2 = 16384;
5368
5369
  function parseSource2(parser, source) {
@@ -5375,6 +5376,11 @@ function makeJsParser2() {
5375
5376
  p.setLanguage(import_tree_sitter_javascript2.default);
5376
5377
  return p;
5377
5378
  }
5379
+ function makePyParser2() {
5380
+ const p = new import_tree_sitter2.default();
5381
+ p.setLanguage(import_tree_sitter_python2.default);
5382
+ return p;
5383
+ }
5378
5384
  var ROUTER_METHODS = /* @__PURE__ */ new Set([
5379
5385
  "get",
5380
5386
  "post",
@@ -5387,6 +5393,7 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
5387
5393
  ]);
5388
5394
  var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
5389
5395
  var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
5396
+ var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
5390
5397
  function canonicalizeTemplate(raw) {
5391
5398
  let p = raw.split("?")[0].split("#")[0];
5392
5399
  if (!p.startsWith("/")) p = "/" + p;
@@ -5614,8 +5621,102 @@ function nextRoutesFromFile(source, relFile, parser) {
5614
5621
  }
5615
5622
  return [];
5616
5623
  }
5624
+ function pyStaticStringText(node) {
5625
+ if (node.type !== "string") return null;
5626
+ for (let i = 0; i < node.namedChildCount; i++) {
5627
+ const child = node.namedChild(i);
5628
+ if (child?.type === "interpolation") return null;
5629
+ if (child?.type === "string_content") return child.text;
5630
+ }
5631
+ return "";
5632
+ }
5633
+ function keywordArrayStrings(argsNode, key) {
5634
+ for (let i = 0; i < argsNode.namedChildCount; i++) {
5635
+ const arg = argsNode.namedChild(i);
5636
+ if (arg?.type !== "keyword_argument") continue;
5637
+ if (arg.childForFieldName("name")?.text !== key) continue;
5638
+ const val = arg.childForFieldName("value");
5639
+ if (!val || val.type !== "list") return [];
5640
+ const out = [];
5641
+ for (let j = 0; j < val.namedChildCount; j++) {
5642
+ const el = val.namedChild(j);
5643
+ if (el?.type === "string") {
5644
+ const s = pyStaticStringText(el);
5645
+ if (s) out.push(s);
5646
+ }
5647
+ }
5648
+ return out;
5649
+ }
5650
+ return [];
5651
+ }
5652
+ function collectApiRouterPrefixes(root) {
5653
+ const prefixes = /* @__PURE__ */ new Map();
5654
+ walk(root, (node) => {
5655
+ if (node.type !== "assignment") return;
5656
+ const right = node.childForFieldName("right");
5657
+ if (!right || right.type !== "call") return;
5658
+ const fn = right.childForFieldName("function");
5659
+ if (!fn) return;
5660
+ const ctor = fn.type === "attribute" ? fn.childForFieldName("attribute")?.text : fn.text;
5661
+ if (ctor !== "APIRouter") return;
5662
+ const left = node.childForFieldName("left");
5663
+ if (!left || left.type !== "identifier") return;
5664
+ const args = right.childForFieldName("arguments");
5665
+ if (!args) return;
5666
+ for (let i = 0; i < args.namedChildCount; i++) {
5667
+ const arg = args.namedChild(i);
5668
+ if (arg?.type !== "keyword_argument") continue;
5669
+ if (arg.childForFieldName("name")?.text !== "prefix") continue;
5670
+ const val = arg.childForFieldName("value");
5671
+ const p = val ? pyStaticStringText(val) : null;
5672
+ if (p !== null) prefixes.set(left.text, p);
5673
+ }
5674
+ });
5675
+ return prefixes;
5676
+ }
5677
+ function fastapiRoutesFromSource(source, parser) {
5678
+ const tree = parseSource2(parser, source);
5679
+ const prefixes = collectApiRouterPrefixes(tree.rootNode);
5680
+ const out = [];
5681
+ walk(tree.rootNode, (node) => {
5682
+ if (node.type !== "decorator") return;
5683
+ const call = node.namedChild(0);
5684
+ if (!call || call.type !== "call") return;
5685
+ const fn = call.childForFieldName("function");
5686
+ if (!fn || fn.type !== "attribute") return;
5687
+ const method = fn.childForFieldName("attribute")?.text?.toLowerCase();
5688
+ if (!method) return;
5689
+ const isVerb = FASTAPI_METHODS.has(method);
5690
+ if (!isVerb && method !== "api_route") return;
5691
+ const args = call.childForFieldName("arguments");
5692
+ const first = args?.namedChild(0);
5693
+ if (!first || first.type !== "string") return;
5694
+ const rawPath = pyStaticStringText(first);
5695
+ if (rawPath === null || !rawPath.startsWith("/")) return;
5696
+ const obj = fn.childForFieldName("object")?.text;
5697
+ const prefix = obj ? prefixes.get(obj) ?? "" : "";
5698
+ const pathTemplate = canonicalizeTemplate(prefix + rawPath);
5699
+ const line = node.startPosition.row + 1;
5700
+ if (isVerb) {
5701
+ out.push({ method: method.toUpperCase(), pathTemplate, line, framework: "fastapi" });
5702
+ return;
5703
+ }
5704
+ const methods = keywordArrayStrings(args, "methods");
5705
+ const list = methods.length > 0 ? methods : ["ALL"];
5706
+ for (const m of list) {
5707
+ out.push({
5708
+ method: m === "ALL" ? "ALL" : m.toUpperCase(),
5709
+ pathTemplate,
5710
+ line,
5711
+ framework: "fastapi"
5712
+ });
5713
+ }
5714
+ });
5715
+ return out;
5716
+ }
5617
5717
  async function addRoutes(graph, services) {
5618
5718
  const jsParser = makeJsParser2();
5719
+ const pyParser = makePyParser2();
5619
5720
  let nodesAdded = 0;
5620
5721
  let edgesAdded = 0;
5621
5722
  for (const service of services) {
@@ -5627,15 +5728,20 @@ async function addRoutes(graph, services) {
5627
5728
  const hasFastify = deps["fastify"] !== void 0;
5628
5729
  const hasHono = deps["hono"] !== void 0;
5629
5730
  const hasNext = deps["next"] !== void 0;
5630
- if (!hasExpress && !hasFastify && !hasHono && !hasNext) continue;
5731
+ const hasFastapi = deps["fastapi"] !== void 0;
5732
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasFastapi) continue;
5631
5733
  const files = await loadSourceFiles(service.dir);
5632
5734
  for (const file of files) {
5633
5735
  if (isTestPath(file.path)) continue;
5634
- if (!JS_ROUTE_EXTENSIONS.has(import_node_path23.default.extname(file.path))) continue;
5736
+ const ext = import_node_path23.default.extname(file.path);
5737
+ const isPy = ext === ".py";
5738
+ if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy) continue;
5635
5739
  const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
5636
5740
  let routes;
5637
5741
  try {
5638
- if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5742
+ if (isPy) {
5743
+ routes = hasFastapi ? fastapiRoutesFromSource(file.content, pyParser) : [];
5744
+ } else if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
5639
5745
  routes = nextRoutesFromFile(file.content, relFile, jsParser);
5640
5746
  } else if (hasExpress || hasFastify || hasHono) {
5641
5747
  routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
@@ -5819,7 +5925,7 @@ init_cjs_shims();
5819
5925
  var import_node_path25 = __toESM(require("path"), 1);
5820
5926
  var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5821
5927
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
5822
- var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5928
+ var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
5823
5929
  var import_types13 = require("@neat.is/types");
5824
5930
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
5825
5931
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
@@ -5874,14 +5980,14 @@ function makeJsParser3() {
5874
5980
  p.setLanguage(import_tree_sitter_javascript3.default);
5875
5981
  return p;
5876
5982
  }
5877
- function makePyParser2() {
5983
+ function makePyParser3() {
5878
5984
  const p = new import_tree_sitter3.default();
5879
- p.setLanguage(import_tree_sitter_python2.default);
5985
+ p.setLanguage(import_tree_sitter_python3.default);
5880
5986
  return p;
5881
5987
  }
5882
5988
  async function addHttpCallEdges(graph, services) {
5883
5989
  const jsParser = makeJsParser3();
5884
- const pyParser = makePyParser2();
5990
+ const pyParser = makePyParser3();
5885
5991
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
5886
5992
  let nodesAdded = 0;
5887
5993
  let edgesAdded = 0;