diff --git a/packages/argent/package.json b/packages/argent/package.json
index c794d88f..280917a8 100644
--- a/packages/argent/package.json
+++ b/packages/argent/package.json
@@ -41,7 +41,9 @@
"scripts/postinstall.cjs"
],
"dependencies": {
- "@modelcontextprotocol/sdk": "^1.20.0"
+ "@modelcontextprotocol/sdk": "^1.20.0",
+ "tree-sitter": "^0.21.1",
+ "tree-sitter-typescript": "^0.23.2"
},
"devDependencies": {
"@argent/cli": "file:../argent-cli",
diff --git a/packages/argent/scripts/bundle-tools.cjs b/packages/argent/scripts/bundle-tools.cjs
index 8aa7609f..a847e2c0 100644
--- a/packages/argent/scripts/bundle-tools.cjs
+++ b/packages/argent/scripts/bundle-tools.cjs
@@ -119,6 +119,10 @@ esbuild.buildSync({
outfile: OUT_FILE,
alias: ALIASES,
mainFields: MAIN_FIELDS,
+ // tree-sitter / tree-sitter-typescript are native addons (.node) that esbuild
+ // cannot inline; keep them external so the bundle `require()`s them at runtime
+ // from the published package's own dependencies (see package.json).
+ external: ["tree-sitter", "tree-sitter-typescript"],
});
console.log(`✓ Bundled tools server → ${path.relative(process.cwd(), OUT_FILE)}`);
diff --git a/packages/tool-server/src/tools/profiler/react/react-profiler-component-source.ts b/packages/tool-server/src/tools/profiler/react/react-profiler-component-source.ts
index aa2c6416..844995cd 100644
--- a/packages/tool-server/src/tools/profiler/react/react-profiler-component-source.ts
+++ b/packages/tool-server/src/tools/profiler/react/react-profiler-component-source.ts
@@ -23,10 +23,20 @@ Returns found: false if the component is not found in user-owned code (e.g. live
const entry = astIndex.index.get(params.component_name);
if (!entry) {
+ if (!astIndex.treeSitterAvailable) {
+ return {
+ found: false,
+ component: params.component_name,
+ message:
+ `Component source lookup is unavailable: the tree-sitter parser could not be loaded, ` +
+ `so no source files were indexed. Ensure @swmansion/argent's "tree-sitter" and ` +
+ `"tree-sitter-typescript" dependencies are installed.`,
+ };
+ }
return {
found: false,
component: params.component_name,
- message: `Component "${params.component_name}" not found in ${params.project_root}`,
+ message: `Component "${params.component_name}" not found in ${params.project_root} (searched ${astIndex.indexedFiles} files).`,
};
}
diff --git a/packages/tool-server/test/react-profiler/ast-index.test.ts b/packages/tool-server/test/react-profiler/ast-index.test.ts
new file mode 100644
index 00000000..10f26029
--- /dev/null
+++ b/packages/tool-server/test/react-profiler/ast-index.test.ts
@@ -0,0 +1,38 @@
+import { describe, it, expect } from "vitest";
+import { mkdtempSync, mkdirSync, writeFileSync } from "fs";
+import { tmpdir } from "os";
+import { join } from "path";
+import { buildAstIndexWithDiagnostics } from "../../src/utils/react-profiler/pipeline/06-resolve/ast-index";
+
+/**
+ * react-profiler-component-source returned found:false for every component in a
+ * standard TS/TSX Expo project because @swmansion/argent never declared (or
+ * shipped) tree-sitter / tree-sitter-typescript, so the parser require() threw,
+ * was swallowed, and the index was always empty. The grammar/match logic itself
+ * is correct — this test guards it for the idiomatic declaration forms and, via
+ * treeSitterAvailable, fails loudly if the parser dependency goes missing again.
+ */
+describe("buildAstIndexWithDiagnostics", () => {
+ it("indexes export-default-function, plain-function, and arrow TSX components", async () => {
+ const dir = mkdtempSync(join(tmpdir(), "ast-index-"));
+ mkdirSync(join(dir, "components"), { recursive: true });
+ writeFileSync(
+ join(dir, "components", "foo.tsx"),
+ [
+ `import React from "react";`,
+ `type P = { x: number };`,
+ `export default function Foo({ x }: P) { return {x}; }`,
+ `function Bar({ y }: { y: number }) { return {y}; }`,
+ `const Baz = ({ z }: { z: number }) => {z};`,
+ ``,
+ ].join("\n")
+ );
+
+ const res = await buildAstIndexWithDiagnostics(dir);
+
+ expect(res.treeSitterAvailable).toBe(true);
+ expect(res.index.get("Foo")?.line).toBe(3);
+ expect(res.index.get("Bar")?.line).toBe(4);
+ expect(res.index.get("Baz")?.line).toBe(5);
+ });
+});