@mrclrchtr/supi-tree-sitter 0.1.0

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.
Files changed (51) hide show
  1. package/README.md +97 -0
  2. package/package.json +67 -0
  3. package/resources/.gitkeep +0 -0
  4. package/resources/grammars/bash/tree-sitter-bash.wasm +0 -0
  5. package/resources/grammars/bash/tree-sitter-bash.wasm.json +7 -0
  6. package/resources/grammars/c/tree-sitter-c.wasm +0 -0
  7. package/resources/grammars/c/tree-sitter-c.wasm.json +7 -0
  8. package/resources/grammars/cpp/tree-sitter-cpp.wasm +0 -0
  9. package/resources/grammars/cpp/tree-sitter-cpp.wasm.json +7 -0
  10. package/resources/grammars/go/tree-sitter-go.wasm +0 -0
  11. package/resources/grammars/go/tree-sitter-go.wasm.json +7 -0
  12. package/resources/grammars/html/tree-sitter-html.wasm +0 -0
  13. package/resources/grammars/html/tree-sitter-html.wasm.json +7 -0
  14. package/resources/grammars/java/tree-sitter-java.wasm +0 -0
  15. package/resources/grammars/java/tree-sitter-java.wasm.json +7 -0
  16. package/resources/grammars/javascript/tree-sitter-javascript.wasm +0 -0
  17. package/resources/grammars/javascript/tree-sitter-javascript.wasm.json +7 -0
  18. package/resources/grammars/kotlin/tree-sitter-kotlin.wasm +0 -0
  19. package/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +12 -0
  20. package/resources/grammars/python/tree-sitter-python.wasm +0 -0
  21. package/resources/grammars/python/tree-sitter-python.wasm.json +7 -0
  22. package/resources/grammars/r/tree-sitter-r.wasm +0 -0
  23. package/resources/grammars/r/tree-sitter-r.wasm.json +7 -0
  24. package/resources/grammars/ruby/tree-sitter-ruby.wasm +0 -0
  25. package/resources/grammars/ruby/tree-sitter-ruby.wasm.json +7 -0
  26. package/resources/grammars/rust/tree-sitter-rust.wasm +0 -0
  27. package/resources/grammars/rust/tree-sitter-rust.wasm.json +7 -0
  28. package/resources/grammars/sql/tree-sitter-sql.wasm +0 -0
  29. package/resources/grammars/sql/tree-sitter-sql.wasm.json +19 -0
  30. package/resources/grammars/tsx/tree-sitter-tsx.wasm +0 -0
  31. package/resources/grammars/tsx/tree-sitter-tsx.wasm.json +7 -0
  32. package/resources/grammars/typescript/tree-sitter-typescript.wasm +0 -0
  33. package/resources/grammars/typescript/tree-sitter-typescript.wasm.json +7 -0
  34. package/scripts/generate-kotlin-wasm.mjs +126 -0
  35. package/scripts/generate-sql-wasm.mjs +144 -0
  36. package/scripts/vendor-wasm.mjs +151 -0
  37. package/src/callees.ts +343 -0
  38. package/src/coordinates.ts +108 -0
  39. package/src/exports.ts +315 -0
  40. package/src/formatting.ts +104 -0
  41. package/src/imports.ts +42 -0
  42. package/src/index.ts +16 -0
  43. package/src/language.ts +116 -0
  44. package/src/node-at.ts +96 -0
  45. package/src/outline.ts +287 -0
  46. package/src/runtime.ts +237 -0
  47. package/src/session.ts +112 -0
  48. package/src/structure.ts +7 -0
  49. package/src/syntax-node.ts +13 -0
  50. package/src/tree-sitter.ts +306 -0
  51. package/src/types.ts +146 -0
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # @mrclrchtr/supi-tree-sitter
2
+
3
+ Tree-sitter structural analysis for the [pi coding agent](https://github.com/earendil-works/pi).
4
+
5
+ This package registers a `tree_sitter` tool and also exports a small TypeScript service API for other SuPi extensions. It is designed as a standalone structural-analysis substrate: it does not depend on `supi-lsp` or semantic language-server tooling, and it remains correct and useful when installed by itself.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pi install npm:@mrclrchtr/supi-tree-sitter
11
+ ```
12
+
13
+ Standalone installs include the runtime grammar dependencies needed for the supported non-vendored languages. Kotlin and SQL use vendored WASM grammars bundled with this package.
14
+
15
+ It is also bundled by the full SuPi meta-package:
16
+
17
+ ```bash
18
+ pi install npm:@mrclrchtr/supi
19
+ ```
20
+
21
+ ## Supported files
22
+
23
+ The runtime can parse these file families:
24
+
25
+ - **JavaScript / TypeScript** — `.ts`, `.tsx`, `.mts`, `.cts`, `.js`, `.jsx`, `.mjs`, `.cjs`
26
+ - **Python** — `.py`, `.pyi`
27
+ - **Rust** — `.rs`
28
+ - **Go** — `.go`, `.mod`
29
+ - **C / C++** — `.c`, `.h`, `.cpp`, `.hpp`, `.cc`, `.cxx`, `.hxx`, `.c++`, `.h++`
30
+ - **Java** — `.java`
31
+ - **Kotlin** — `.kt`, `.kts`
32
+ - **Ruby** — `.rb`
33
+ - **Bash / Shell** — `.sh`, `.bash`, `.zsh`
34
+ - **HTML** — `.html`, `.htm`, `.xhtml`
35
+ - **R** — `.r`
36
+ - **SQL** — `.sql`
37
+
38
+ Grammar `.wasm` files are resolved from installed package metadata for npm-shipped grammars, not from repository-relative paths.
39
+
40
+ ## `tree_sitter` tool
41
+
42
+ Actions:
43
+
44
+ - `outline` — list structural declarations such as functions, classes, interfaces, methods, and variables (**currently JavaScript / TypeScript only**)
45
+ - `imports` — list import statements and module specifiers (**currently JavaScript / TypeScript only**)
46
+ - `exports` — list exported declarations, re-exports, and TypeScript `export =` assignments (**currently JavaScript / TypeScript only**)
47
+ - `node_at` — return the smallest syntax node at a 1-based `line`/`character` position, plus ancestry (all supported grammars)
48
+ - `query` — run a custom Tree-sitter query and return captures (all supported grammars)
49
+ - `callees` — find outgoing function/method calls from a position; supports all grammars with a callee query configured
50
+
51
+ Coordinates are 1-based and compatible with the `lsp` tool. `character` is a UTF-16 code-unit column. Relative file paths resolve from the pi session working directory.
52
+
53
+ Large result sets are capped at 100 emitted items per tool response. For outlines, nested children count toward the same cap so deeply nested classes do not bypass the limit.
54
+
55
+ ## Service API
56
+
57
+ ```ts
58
+ import { createTreeSitterSession } from "@mrclrchtr/supi-tree-sitter";
59
+
60
+ const session = createTreeSitterSession(process.cwd());
61
+ try {
62
+ const parseable = await session.canParse("src/index.ts");
63
+ if (parseable.kind === "success") {
64
+ console.log(parseable.data.file, parseable.data.language);
65
+ }
66
+
67
+ const outline = await session.outline("src/index.ts");
68
+ if (outline.kind === "success") {
69
+ console.log(outline.data);
70
+ }
71
+
72
+ const callees = await session.calleesAt("src/index.ts", 1, 10);
73
+ if (callees.kind === "success") {
74
+ console.log(callees.data.enclosingScope.name, callees.data.callees);
75
+ }
76
+ } finally {
77
+ session.dispose();
78
+ }
79
+ ```
80
+
81
+ `canParse(file)` validates that a supported file can be read and parsed, then returns the resolved file path and grammar id. It does not expose the raw Tree-sitter tree; use `outline`, `query`, `imports`, `exports`, `nodeAt`, or `calleesAt` for structured results.
82
+
83
+ `calleesAt(file, line, character)` extracts structural outgoing calls from the enclosing function/method scope at the given position. It returns the enclosing scope name and a deduplicated list of callees with their source ranges.
84
+
85
+ Exported types include `TreeSitterResult`, `TreeSitterSession`, `OutlineItem`, `ImportRecord`, `ExportRecord`, `NodeAtResult`, `QueryCapture`, `CalleesAtResult`, `SourceRange`, `GrammarId`, and `SupportedExtension`.
86
+
87
+ Always call `dispose()` when the session is no longer needed. The runtime lazily initializes grammars, reuses parser instances within a session, deduplicates concurrent first-use grammar initialization, and retries after initialization failures.
88
+
89
+ ## Positioning
90
+
91
+ `supi-tree-sitter` is the structural-analysis substrate in SuPi's layered code-understanding stack:
92
+
93
+ - `supi-tree-sitter` — parser-backed structural analysis (this package)
94
+ - `supi-lsp` — live semantic analysis through language servers
95
+ - `supi-code-intelligence` — higher-level agent-facing analysis built on top of both substrates
96
+
97
+ Each substrate can be installed and used independently. `supi-tree-sitter` does not require `supi-lsp` to be present, and its prompt guidance is written so it remains correct in standalone installs.
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@mrclrchtr/supi-tree-sitter",
3
+ "version": "0.1.0",
4
+ "description": "SuPi Tree-sitter extension — structural AST analysis for pi",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/mrclrchtr/supi.git"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "keywords": [
14
+ "pi-package",
15
+ "pi",
16
+ "pi-coding-agent"
17
+ ],
18
+ "files": [
19
+ "src/**/*.ts",
20
+ "src/**/*.json",
21
+ "resources",
22
+ "scripts/*.mjs",
23
+ "!__tests__"
24
+ ],
25
+ "scripts": {
26
+ "vendor:wasm": "node scripts/vendor-wasm.mjs",
27
+ "check:wasm": "node scripts/vendor-wasm.mjs --check",
28
+ "generate:kotlin-wasm": "node scripts/generate-kotlin-wasm.mjs",
29
+ "check:kotlin-wasm": "node scripts/generate-kotlin-wasm.mjs --check",
30
+ "generate:sql-wasm": "node scripts/generate-sql-wasm.mjs",
31
+ "check:sql-wasm": "node scripts/generate-sql-wasm.mjs --check"
32
+ },
33
+ "dependencies": {
34
+ "web-tree-sitter": "^0.26.8"
35
+ },
36
+ "peerDependencies": {
37
+ "@earendil-works/pi-ai": "*",
38
+ "@earendil-works/pi-coding-agent": "*",
39
+ "typebox": "*"
40
+ },
41
+ "devDependencies": {
42
+ "@davisvaughan/tree-sitter-r": "^1.2.0",
43
+ "@derekstride/tree-sitter-sql": "^0.3.11",
44
+ "@types/node": "^25.6.0",
45
+ "tree-sitter-bash": "^0.23.0",
46
+ "tree-sitter-c": "^0.23.0",
47
+ "tree-sitter-cli": "0.22.6",
48
+ "tree-sitter-cpp": "^0.23.0",
49
+ "tree-sitter-go": "^0.23.0",
50
+ "tree-sitter-html": "^0.23.0",
51
+ "tree-sitter-java": "^0.23.0",
52
+ "tree-sitter-javascript": "^0.23.0",
53
+ "tree-sitter-kotlin": "^0.3.8",
54
+ "tree-sitter-python": "^0.23.0",
55
+ "tree-sitter-ruby": "^0.23.0",
56
+ "tree-sitter-rust": "^0.23.0",
57
+ "tree-sitter-typescript": "^0.23.0",
58
+ "vitest": "^4.1.4",
59
+ "@mrclrchtr/supi-test-utils": "workspace:*"
60
+ },
61
+ "pi": {
62
+ "extensions": [
63
+ "./src/tree-sitter.ts"
64
+ ]
65
+ },
66
+ "main": "src/index.ts"
67
+ }
File without changes
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-bash",
4
+ "version": "0.23.3"
5
+ },
6
+ "sha256": "d1844429a58620f306b6f42aebe92298243ca8120cd833a3ab5d87c7a2e7b9fd"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-c",
4
+ "version": "0.23.6"
5
+ },
6
+ "sha256": "146f85977800935f18b06940518b16ded13cf4007ef0e3190573b969a98b9adc"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-cpp",
4
+ "version": "0.23.4"
5
+ },
6
+ "sha256": "174eb0deb75b2ec7881bcacda9f995648d8e683956e5c2267e69ab6dc503fcbf"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-go",
4
+ "version": "0.23.4"
5
+ },
6
+ "sha256": "6dfc8eacdad0a54d0cad0d888851bd19cdd14d82582f110f888bbf6f9e5e2d64"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-html",
4
+ "version": "0.23.2"
5
+ },
6
+ "sha256": "c48fcd82c7ea8bf943180088ba7f28c48b2bb5287874179168bf9d31e394cf85"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-java",
4
+ "version": "0.23.5"
5
+ },
6
+ "sha256": "4fdeac4ca6ca089f06c6f7e562abcac1733cd465728cc7031ebb73c2019122c4"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-javascript",
4
+ "version": "0.23.1"
5
+ },
6
+ "sha256": "4a378293fe7853cbee2836023be072dafa0e53b3b5edb245920838ca834ed121"
7
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-kotlin",
4
+ "version": "0.3.8",
5
+ "repository": "https://github.com/fwcd/tree-sitter-kotlin",
6
+ "releaseAsset": "https://github.com/fwcd/tree-sitter-kotlin/releases/download/0.3.8/tree-sitter-kotlin.wasm"
7
+ },
8
+ "generatedWith": {
9
+ "treeSitterCli": "0.22.6"
10
+ },
11
+ "sha256": "c624e7443b371c28adc5d81674e73067564c12555ebe3ed96a6c8db814b7602d"
12
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-python",
4
+ "version": "0.23.6"
5
+ },
6
+ "sha256": "8c93692fb368e288a5824cee55773c9b3602804f513bda48c97661e52e9c2da2"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "@davisvaughan/tree-sitter-r",
4
+ "version": "1.2.0"
5
+ },
6
+ "sha256": "2a8f5acd1c53d91e0ec5c01a6830d8ac7f5a7f96f0ac4b3768c016c8e9d07711"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-ruby",
4
+ "version": "0.23.1"
5
+ },
6
+ "sha256": "09a96427d7c72f0613ed470cd9812223fc4a91d6a9c025c0235cc6bd59ff96f4"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-rust",
4
+ "version": "0.23.3"
5
+ },
6
+ "sha256": "f65f354215611fd94ad34134b3427eb3d58cbb745df7b6509ba722184db73d57"
7
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "@derekstride/tree-sitter-sql",
4
+ "version": "0.3.11",
5
+ "repository": "https://github.com/derekstride/tree-sitter-sql"
6
+ },
7
+ "generatedWith": {
8
+ "treeSitterCli": "0.22.6"
9
+ },
10
+ "sha256": "0871a3bd488ed39f5a521484337f07a43bd6aa95dbdf1cdd24744deecafa58fc",
11
+ "trust": {
12
+ "note": "The @derekstride/tree-sitter-sql npm package is a devDependency only. It is never resolved at runtime — the vendored WASM above is the sole runtime artifact. The npm package's install script uses 'npx --yes' which some package managers flag as a supply-chain risk; pnpm ignores build scripts by default, so this script never runs during install. The WASM was built locally from the package source (not downloaded from npm). See scripts/generate-sql-wasm.mjs for the rebuild procedure.",
13
+ "alternativesConsidered": [
14
+ "tree-sitter-sql (m-novikov, 2021, stale)",
15
+ "tree-sitter-sql-bigquery (BigQuery-specific)",
16
+ "dialect-specific grammars (too narrow)"
17
+ ]
18
+ }
19
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-typescript",
4
+ "version": "0.23.2"
5
+ },
6
+ "sha256": "79e5da75ea62855a0cd67177685f0164eac87d5f630b3cbe1e0a099751ad30f8"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "source": {
3
+ "npmPackage": "tree-sitter-typescript",
4
+ "version": "0.23.2"
5
+ },
6
+ "sha256": "778025db5a8be0e70f8ccc3671e486dfeddd048c25d9e8a70c26de2e1bf6f97d"
7
+ }
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { createHash } from "node:crypto";
5
+ import * as fs from "node:fs";
6
+ import { createRequire } from "node:module";
7
+ import * as os from "node:os";
8
+ import * as path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const require = createRequire(import.meta.url);
12
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
13
+ const packageRoot = path.resolve(scriptDir, "..");
14
+ const wasmDir = path.join(packageRoot, "resources", "grammars", "kotlin");
15
+ const wasmPath = path.join(wasmDir, "tree-sitter-kotlin.wasm");
16
+ const metadataPath = path.join(wasmDir, "tree-sitter-kotlin.wasm.json");
17
+ const checkMode = process.argv.includes("--check");
18
+
19
+ function readPackage(packageName) {
20
+ const packageJsonPath = require.resolve(`${packageName}/package.json`);
21
+ return {
22
+ dir: path.dirname(packageJsonPath),
23
+ json: JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")),
24
+ };
25
+ }
26
+
27
+ function sha256(filePath) {
28
+ return createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
29
+ }
30
+
31
+ function readMetadata() {
32
+ return JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
33
+ }
34
+
35
+ function expectedReleaseAsset(version) {
36
+ return `https://github.com/fwcd/tree-sitter-kotlin/releases/download/${version}/tree-sitter-kotlin.wasm`;
37
+ }
38
+
39
+ function assertKotlinWasmCurrent() {
40
+ const kotlinPackage = readPackage("tree-sitter-kotlin");
41
+ const metadata = readMetadata();
42
+ const actualSha = sha256(wasmPath);
43
+ const errors = [];
44
+
45
+ if (metadata.source?.npmPackage !== "tree-sitter-kotlin") {
46
+ errors.push("metadata source package must be tree-sitter-kotlin");
47
+ }
48
+ if (metadata.source?.version !== kotlinPackage.json.version) {
49
+ errors.push(
50
+ `metadata pins tree-sitter-kotlin ${metadata.source?.version}, but installed package is ${kotlinPackage.json.version}`,
51
+ );
52
+ }
53
+ if (metadata.source?.releaseAsset !== expectedReleaseAsset(kotlinPackage.json.version)) {
54
+ errors.push(
55
+ "metadata release asset URL does not match the installed tree-sitter-kotlin version",
56
+ );
57
+ }
58
+ if (metadata.sha256 !== actualSha) {
59
+ errors.push(`metadata sha256 ${metadata.sha256} does not match vendored file ${actualSha}`);
60
+ }
61
+
62
+ if (errors.length > 0) {
63
+ throw new Error(
64
+ `Vendored Kotlin Tree-sitter WASM is stale:\n- ${errors.join("\n- ")}\nRun: pnpm --filter @mrclrchtr/supi-tree-sitter generate:kotlin-wasm`,
65
+ );
66
+ }
67
+
68
+ process.stdout.write(
69
+ `Kotlin Tree-sitter WASM is current (${kotlinPackage.json.version}, ${actualSha}).\n`,
70
+ );
71
+ }
72
+
73
+ function generateKotlinWasm() {
74
+ const kotlinPackage = readPackage("tree-sitter-kotlin");
75
+ const cliPackage = readPackage("tree-sitter-cli");
76
+ const cliPath = path.join(cliPackage.dir, "cli.js");
77
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "supi-kotlin-wasm-"));
78
+ const grammarDir = path.join(tempRoot, "tree-sitter-kotlin");
79
+
80
+ try {
81
+ fs.cpSync(kotlinPackage.dir, grammarDir, { recursive: true });
82
+ const result = spawnSync(process.execPath, [cliPath, "build", "--wasm"], {
83
+ cwd: grammarDir,
84
+ stdio: "inherit",
85
+ });
86
+
87
+ if (result.status !== 0) {
88
+ throw new Error(
89
+ "tree-sitter build --wasm failed. Install Docker or Emscripten, then rerun generate:kotlin-wasm.",
90
+ );
91
+ }
92
+
93
+ const generatedWasmPath = path.join(grammarDir, "tree-sitter-kotlin.wasm");
94
+ if (!fs.existsSync(generatedWasmPath)) {
95
+ throw new Error(`Expected generated WASM at ${generatedWasmPath}`);
96
+ }
97
+
98
+ fs.mkdirSync(wasmDir, { recursive: true });
99
+ fs.copyFileSync(generatedWasmPath, wasmPath);
100
+
101
+ const checksum = sha256(wasmPath);
102
+ const metadata = {
103
+ source: {
104
+ npmPackage: "tree-sitter-kotlin",
105
+ version: kotlinPackage.json.version,
106
+ repository: "https://github.com/fwcd/tree-sitter-kotlin",
107
+ releaseAsset: expectedReleaseAsset(kotlinPackage.json.version),
108
+ },
109
+ generatedWith: {
110
+ treeSitterCli: cliPackage.json.version,
111
+ },
112
+ sha256: checksum,
113
+ };
114
+ fs.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`);
115
+ process.stdout.write(`Generated ${wasmPath}\n`);
116
+ process.stdout.write(`SHA256 ${checksum}\n`);
117
+ } finally {
118
+ fs.rmSync(tempRoot, { recursive: true, force: true });
119
+ }
120
+ }
121
+
122
+ if (checkMode) {
123
+ assertKotlinWasmCurrent();
124
+ } else {
125
+ generateKotlinWasm();
126
+ }
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SQL Tree-sitter WASM generator
4
+ *
5
+ * Rebuilds the vendored SQL grammar WASM from the @derekstride/tree-sitter-sql
6
+ * npm package. This package is a devDependency ONLY — the vendored WASM is the
7
+ * sole runtime artifact. The npm package is never resolved at runtime.
8
+ *
9
+ * Trust considerations:
10
+ * - The @derekstride/tree-sitter-sql install script uses "npx --yes", which
11
+ * some package managers flag as a supply-chain risk. pnpm ignores build
12
+ * scripts by default, so this script never runs during install.
13
+ * - The WASM is built locally from the installed package source (not downloaded
14
+ * from npm). The build uses tree-sitter-cli 0.22.6 with Emscripten/Docker.
15
+ * - Alternatives considered: tree-sitter-sql (m-novikov, stale since 2021),
16
+ * tree-sitter-sql-bigquery (dialect-specific), and dialect-specific grammars.
17
+ * derekstride/tree-sitter-sql is the most mature general-purpose SQL grammar.
18
+ *
19
+ * Usage:
20
+ * pnpm --filter @mrclrchtr/supi-tree-sitter generate:sql-wasm
21
+ * pnpm --filter @mrclrchtr/supi-tree-sitter check:sql-wasm
22
+ */
23
+
24
+ import { spawnSync } from "node:child_process";
25
+ import { createHash } from "node:crypto";
26
+ import * as fs from "node:fs";
27
+ import { createRequire } from "node:module";
28
+ import * as os from "node:os";
29
+ import * as path from "node:path";
30
+ import { fileURLToPath } from "node:url";
31
+
32
+ const require = createRequire(import.meta.url);
33
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
34
+ const packageRoot = path.resolve(scriptDir, "..");
35
+ const wasmDir = path.join(packageRoot, "resources", "grammars", "sql");
36
+ const wasmPath = path.join(wasmDir, "tree-sitter-sql.wasm");
37
+ const metadataPath = path.join(wasmDir, "tree-sitter-sql.wasm.json");
38
+ const checkMode = process.argv.includes("--check");
39
+
40
+ function readPackage(packageName) {
41
+ const packageJsonPath = require.resolve(`${packageName}/package.json`);
42
+ return {
43
+ dir: path.dirname(packageJsonPath),
44
+ json: JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")),
45
+ };
46
+ }
47
+
48
+ function sha256(filePath) {
49
+ return createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
50
+ }
51
+
52
+ function readMetadata() {
53
+ return JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
54
+ }
55
+
56
+ function assertSqlWasmCurrent() {
57
+ const sqlPackage = readPackage("@derekstride/tree-sitter-sql");
58
+ const metadata = readMetadata();
59
+ const actualSha = sha256(wasmPath);
60
+ const errors = [];
61
+
62
+ if (metadata.source?.npmPackage !== "@derekstride/tree-sitter-sql") {
63
+ errors.push("metadata source package must be @derekstride/tree-sitter-sql");
64
+ }
65
+ if (metadata.source?.version !== sqlPackage.json.version) {
66
+ errors.push(
67
+ `metadata pins @derekstride/tree-sitter-sql ${metadata.source?.version}, but installed package is ${sqlPackage.json.version}`,
68
+ );
69
+ }
70
+ if (metadata.sha256 !== actualSha) {
71
+ errors.push(`metadata sha256 ${metadata.sha256} does not match vendored file ${actualSha}`);
72
+ }
73
+
74
+ if (errors.length > 0) {
75
+ throw new Error(
76
+ `Vendored SQL Tree-sitter WASM is stale:\n- ${errors.join("\n- ")}\nRun: pnpm --filter @mrclrchtr/supi-tree-sitter generate:sql-wasm`,
77
+ );
78
+ }
79
+
80
+ process.stdout.write(
81
+ `SQL Tree-sitter WASM is current (${sqlPackage.json.version}, ${actualSha}).\n`,
82
+ );
83
+ }
84
+
85
+ function generateSqlWasm() {
86
+ const sqlPackage = readPackage("@derekstride/tree-sitter-sql");
87
+ const cliPackage = readPackage("tree-sitter-cli");
88
+ const cliPath = path.join(cliPackage.dir, "cli.js");
89
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "supi-sql-wasm-"));
90
+ const grammarDir = path.join(tempRoot, "tree-sitter-sql");
91
+
92
+ try {
93
+ fs.cpSync(sqlPackage.dir, grammarDir, { recursive: true });
94
+
95
+ // tree-sitter-cli 0.22.6 requires a "tree-sitter" section in package.json
96
+ const grammarPackageJsonPath = path.join(grammarDir, "package.json");
97
+ const grammarPackageJson = JSON.parse(fs.readFileSync(grammarPackageJsonPath, "utf-8"));
98
+ grammarPackageJson["tree-sitter"] = [{ scope: "source.sql" }];
99
+ fs.writeFileSync(grammarPackageJsonPath, JSON.stringify(grammarPackageJson, null, 2));
100
+
101
+ const result = spawnSync(process.execPath, [cliPath, "build", "--wasm"], {
102
+ cwd: grammarDir,
103
+ stdio: "inherit",
104
+ });
105
+
106
+ if (result.status !== 0) {
107
+ throw new Error(
108
+ "tree-sitter build --wasm failed. Install Docker or Emscripten, then rerun generate:sql-wasm.",
109
+ );
110
+ }
111
+
112
+ const generatedWasmPath = path.join(grammarDir, "tree-sitter-sql.wasm");
113
+ if (!fs.existsSync(generatedWasmPath)) {
114
+ throw new Error(`Expected generated WASM at ${generatedWasmPath}`);
115
+ }
116
+
117
+ fs.mkdirSync(wasmDir, { recursive: true });
118
+ fs.copyFileSync(generatedWasmPath, wasmPath);
119
+
120
+ const checksum = sha256(wasmPath);
121
+ const metadata = {
122
+ source: {
123
+ npmPackage: "@derekstride/tree-sitter-sql",
124
+ version: sqlPackage.json.version,
125
+ repository: "https://github.com/derekstride/tree-sitter-sql",
126
+ },
127
+ generatedWith: {
128
+ treeSitterCli: cliPackage.json.version,
129
+ },
130
+ sha256: checksum,
131
+ };
132
+ fs.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`);
133
+ process.stdout.write(`Generated ${wasmPath}\n`);
134
+ process.stdout.write(`SHA256 ${checksum}\n`);
135
+ } finally {
136
+ fs.rmSync(tempRoot, { recursive: true, force: true });
137
+ }
138
+ }
139
+
140
+ if (checkMode) {
141
+ assertSqlWasmCurrent();
142
+ } else {
143
+ generateSqlWasm();
144
+ }