From 56e6f6f15c8d46c6c83e9f0ef7e7d4dc801b8e57 Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Sun, 26 Jul 2026 17:33:25 -0400 Subject: [PATCH] fix(package): vendor grammar WASM so installs need no native toolchain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tree-sitter-solidity@1.2.13 misspells its optional-peer metadata key as `tree_sitter`, so npm reads `tree-sitter` as a *required* peer, installs the native binding, and compiles it from source — which fails against Node >= 23 V8 headers ("C++20 or later required"). That broke `npm install -g @0xsequence/codegenie` and the GitHub Action for every consumer as soon as 0.5.0 added the Solidity grammar. pnpm hid it locally: pnpm-workspace.yaml already ignores the missing peer and denies those builds, and the consumer test ran under that same policy, so the npm path was never exercised. codegenie parses through web-tree-sitter and needs only the published WASM, so the grammar packages become devDependencies and the build vendors their WASM into bundled-grammars/. Every installed dependency is now pure JavaScript. - scripts/copy-grammars.mjs vendors the 7 grammars at build time - tree-sitter-service resolves the bundled copy, falling back to the devDependency so a fresh checkout parses before its first build - new test walks the prod closure and fails on any native install hook, binding.gyp, or peer that npm would treat as required - new test pins every documented action tag to the package version Verified with a real `npm install -g` on Node 26 with scripts enabled: 119 packages in 4s, no ERESOLVE, no node-gyp, and all 7 grammars parse from the installed package. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 1 + Makefile | 2 +- README.md | 2 +- .../workflows/codegenie-review-comment.yml | 2 +- examples/workflows/codegenie-review-pr.yml | 2 +- package.json | 17 ++- pnpm-lock.yaml | 32 ++-- pnpm-workspace.yaml | 8 +- scripts/copy-grammars.mjs | 31 ++++ src/repo/tree-sitter/tree-sitter-service.ts | 36 +++-- tests/github-action.test.ts | 19 +++ tests/package-build.test.ts | 140 +++++++++++++----- 12 files changed, 212 insertions(+), 80 deletions(-) create mode 100644 scripts/copy-grammars.mjs diff --git a/.gitignore b/.gitignore index 9d116ea..11bdd5b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ reviews/ evals/fixtures/logs/ node_modules/ dist/ +bundled-grammars/ diff --git a/Makefile b/Makefile index d7bdca6..d61da1e 100644 --- a/Makefile +++ b/Makefile @@ -64,4 +64,4 @@ models-list: build pnpm run models-list clean: - rm -rf dist/ + rm -rf dist/ bundled-grammars/ diff --git a/README.md b/README.md index cfe8f39..de85596 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ jobs: with: ref: ${{ github.event.pull_request.base.sha }} # trusted base; PR head is fetched as review data fetch-depth: 0 - - uses: 0xPolygon/codegenie@v0.5.0 + - uses: 0xPolygon/codegenie@v0.5.1 with: model: "anthropic/claude-opus-5:high" llm-api-key: ${{ secrets.LLM_API_KEY }} diff --git a/examples/workflows/codegenie-review-comment.yml b/examples/workflows/codegenie-review-comment.yml index e15194e..5cc5143 100644 --- a/examples/workflows/codegenie-review-comment.yml +++ b/examples/workflows/codegenie-review-comment.yml @@ -34,7 +34,7 @@ jobs: with: fetch-depth: 0 - - uses: 0xPolygon/codegenie@v0.5.0 + - uses: 0xPolygon/codegenie@v0.5.1 with: model: "anthropic/claude-opus-5:high" llm-api-key: ${{ secrets.LLM_API_KEY }} diff --git a/examples/workflows/codegenie-review-pr.yml b/examples/workflows/codegenie-review-pr.yml index 9bc87a2..a0eeae9 100644 --- a/examples/workflows/codegenie-review-pr.yml +++ b/examples/workflows/codegenie-review-pr.yml @@ -34,7 +34,7 @@ jobs: ref: ${{ github.event.pull_request.base.sha }} fetch-depth: 0 - - uses: 0xPolygon/codegenie@v0.5.0 + - uses: 0xPolygon/codegenie@v0.5.1 with: model: "anthropic/claude-opus-5:high" llm-api-key: ${{ secrets.LLM_API_KEY }} diff --git a/package.json b/package.json index 98e8749..8d6730a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/codegenie", - "version": "0.5.0", + "version": "0.5.1", "description": "High-signal AI code review agent", "type": "module", "bin": { @@ -9,10 +9,11 @@ "files": [ "dist", "bundled-skills", + "bundled-grammars", "models.md" ], "scripts": { - "build": "tsc -p tsconfig.build.json && node scripts/write-version.mjs", + "build": "tsc -p tsconfig.build.json && node scripts/write-version.mjs && node scripts/copy-grammars.mjs", "typecheck": "tsc -p tsconfig.json --noEmit", "check:workflows": "bash scripts/check-workflows.sh", "check": "pnpm run typecheck && pnpm run check:workflows", @@ -32,12 +33,6 @@ "p-limit": "7.3.1", "picomatch": "4.0.5", "smol-toml": "1.7.0", - "tree-sitter-go": "0.25.0", - "tree-sitter-javascript": "0.25.0", - "tree-sitter-python": "0.25.0", - "tree-sitter-rust": "0.24.0", - "tree-sitter-solidity": "1.2.13", - "tree-sitter-typescript": "0.23.2", "web-tree-sitter": "0.26.11", "yaml": "2.9.0", "zod": "4.4.3" @@ -45,6 +40,12 @@ "devDependencies": { "@types/node": "26.1.1", "@types/picomatch": "4.0.3", + "tree-sitter-go": "0.25.0", + "tree-sitter-javascript": "0.25.0", + "tree-sitter-python": "0.25.0", + "tree-sitter-rust": "0.24.0", + "tree-sitter-solidity": "1.2.13", + "tree-sitter-typescript": "0.23.2", "tsx": "4.23.1", "typescript": "7.0.2", "vitest": "4.1.10" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f604fb7..94c9188 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,22 @@ importers: smol-toml: specifier: 1.7.0 version: 1.7.0 + web-tree-sitter: + specifier: 0.26.11 + version: 0.26.11 + yaml: + specifier: 2.9.0 + version: 2.9.0 + zod: + specifier: 4.4.3 + version: 4.4.3 + devDependencies: + '@types/node': + specifier: 26.1.1 + version: 26.1.1 + '@types/picomatch': + specifier: 4.0.3 + version: 4.0.3 tree-sitter-go: specifier: 0.25.0 version: 0.25.0 @@ -44,22 +60,6 @@ importers: tree-sitter-typescript: specifier: 0.23.2 version: 0.23.2 - web-tree-sitter: - specifier: 0.26.11 - version: 0.26.11 - yaml: - specifier: 2.9.0 - version: 2.9.0 - zod: - specifier: 4.4.3 - version: 4.4.3 - devDependencies: - '@types/node': - specifier: 26.1.1 - version: 26.1.1 - '@types/picomatch': - specifier: 4.0.3 - version: 4.0.3 tsx: specifier: 4.23.1 version: 4.23.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a84a959..43afd22 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,8 +2,12 @@ minimumReleaseAgeExclude: - '@earendil-works/pi-ai' # tree-sitter-solidity mis-spells its optional-peer metadata key as -# `tree_sitter`. codegenie resolves only its published WASM through -# web-tree-sitter, so installing the native tree-sitter peer is unnecessary. +# `tree_sitter`, so npm reads `tree-sitter` as a *required* peer, installs the +# native package, and compiles it from source — which fails against Node >= 23 +# V8 headers. codegenie resolves only published WASM through web-tree-sitter, so +# the grammar packages are devDependencies whose WASM the build vendors into +# bundled-grammars/ (scripts/copy-grammars.mjs). An installed codegenie has no +# native dependency; these rules keep contributor installs equally native-free. peerDependencyRules: ignoreMissing: - tree-sitter diff --git a/scripts/copy-grammars.mjs b/scripts/copy-grammars.mjs new file mode 100644 index 0000000..bb9b331 --- /dev/null +++ b/scripts/copy-grammars.mjs @@ -0,0 +1,31 @@ +import { copyFileSync, mkdirSync, rmSync } from "node:fs"; +import { createRequire } from "node:module"; +import path from "node:path"; +import { GRAMMAR_WASM } from "../dist/repo/tree-sitter/tree-sitter-service.js"; + +// codegenie parses through web-tree-sitter, so the only thing it needs from the +// tree-sitter-* grammar packages is their published WASM. Every one of those +// packages is a native-build package (`install: node-gyp-build`), and +// tree-sitter-solidity additionally misspells its optional-peer key as +// `tree_sitter` — which makes npm treat native tree-sitter as a *required* peer, +// install it, and compile it from source. Copying the WASM at build time keeps +// the grammar packages as devDependencies, so an installed codegenie has no +// native dependency and no install script for a consumer to run. +const require = createRequire(import.meta.url); +const root = process.cwd(); +const outputDirectory = path.join(root, "bundled-grammars"); + +rmSync(outputDirectory, { recursive: true, force: true }); +mkdirSync(outputDirectory, { recursive: true }); + +const copied = []; +for (const { package: packageName, file } of Object.values(GRAMMAR_WASM)) { + const destination = path.join(outputDirectory, file); + if (copied.includes(file)) { + continue; + } + copyFileSync(require.resolve(`${packageName}/${file}`), destination); + copied.push(file); +} + +console.log(`Bundled ${copied.length} grammars: ${copied.join(", ")}`); diff --git a/src/repo/tree-sitter/tree-sitter-service.ts b/src/repo/tree-sitter/tree-sitter-service.ts index 9d35ca7..2df185e 100644 --- a/src/repo/tree-sitter/tree-sitter-service.ts +++ b/src/repo/tree-sitter/tree-sitter-service.ts @@ -1,4 +1,7 @@ +import { existsSync } from "node:fs"; import { createRequire } from "node:module"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; import type { ParseInput, ParsedFile } from "../../types.js"; import type { TelemetryRecorder } from "../../telemetry/telemetry-recorder.js"; import { sha256Hex } from "../../util/hashing.js"; @@ -40,16 +43,31 @@ const MAX_PARSE_BYTES = 1_500_000; const PARSE_TIMEOUT_MS = 1_000; const MAX_CACHE_ENTRIES = 128; -const GRAMMAR_WASM: Record = { - go: "tree-sitter-go/tree-sitter-go.wasm", - typescript: "tree-sitter-typescript/tree-sitter-typescript.wasm", - tsx: "tree-sitter-typescript/tree-sitter-tsx.wasm", - javascript: "tree-sitter-javascript/tree-sitter-javascript.wasm", - rust: "tree-sitter-rust/tree-sitter-rust.wasm", - python: "tree-sitter-python/tree-sitter-python.wasm", - solidity: "tree-sitter-solidity/tree-sitter-solidity.wasm" +// The grammar packages are devDependencies: they are native-build packages, and +// tree-sitter-solidity misspells its optional-peer key as `tree_sitter`, so npm +// treats native tree-sitter as a required peer and compiles it from source. The +// build copies their WASM into bundled-grammars/ (scripts/copy-grammars.mjs), +// which ships in the package, leaving an installed codegenie native-free. +export const GRAMMAR_WASM: Record = { + go: { package: "tree-sitter-go", file: "tree-sitter-go.wasm" }, + typescript: { package: "tree-sitter-typescript", file: "tree-sitter-typescript.wasm" }, + tsx: { package: "tree-sitter-typescript", file: "tree-sitter-tsx.wasm" }, + javascript: { package: "tree-sitter-javascript", file: "tree-sitter-javascript.wasm" }, + rust: { package: "tree-sitter-rust", file: "tree-sitter-rust.wasm" }, + python: { package: "tree-sitter-python", file: "tree-sitter-python.wasm" }, + solidity: { package: "tree-sitter-solidity", file: "tree-sitter-solidity.wasm" } }; +const BUNDLED_GRAMMAR_DIRECTORY = fileURLToPath(new URL("../../../bundled-grammars/", import.meta.url)); + +// Prefers the bundled copy an installed package ships; falls back to the +// devDependency so a fresh checkout parses before its first build. +function resolveBundledGrammarWasm(grammarId: GrammarId): string { + const { package: packageName, file } = GRAMMAR_WASM[grammarId]; + const bundled = path.join(BUNDLED_GRAMMAR_DIRECTORY, file); + return existsSync(bundled) ? bundled : require.resolve(`${packageName}/${file}`); +} + export class TreeSitterService { private readonly telemetry: TelemetryRecorder | undefined; private initPromise: Promise | undefined; @@ -178,7 +196,7 @@ export class TreeSitterService { private async loadLanguageUncached(grammarId: GrammarId): Promise { try { await this.init(); - const wasmPath = this.opts.resolveGrammarWasm?.(grammarId) ?? require.resolve(GRAMMAR_WASM[grammarId]); + const wasmPath = this.opts.resolveGrammarWasm?.(grammarId) ?? resolveBundledGrammarWasm(grammarId); return await (this.opts.loadLanguage?.(wasmPath) ?? Language.load(wasmPath)); } catch (error) { this.unavailable.add(grammarId); diff --git a/tests/github-action.test.ts b/tests/github-action.test.ts index 28818ad..a30e805 100644 --- a/tests/github-action.test.ts +++ b/tests/github-action.test.ts @@ -1054,6 +1054,25 @@ describe("GitHub Action and workflow contracts", () => { expect(commentReviewStep?.with?.["trigger-phrase"]).toBe("codegenie review"); }); + // The action installs the npm version read from its own package.json, so a + // documented tag that lags the package ships a codegenie nobody released. + it("pins every documented action reference to the current package version", () => { + const { version } = JSON.parse(readFileSync("package.json", "utf8")) as { version: string }; + // Only real step references — the dogfood workflow mentions the tag in prose + // to explain why it builds from source instead. + const documented = [...workflowPaths, "README.md"] + .flatMap((filePath) => readFileSync(path.resolve(filePath), "utf8") + .split("\n") + .map((line) => line.trim()) + .filter((line) => /^-?\s*uses:\s*0xPolygon\/codegenie@/u.test(line)) + .map((line) => [filePath, line] as const)); + + expect(documented.length).toBeGreaterThan(0); + for (const [filePath, line] of documented) { + expect(line, filePath).toContain(`0xPolygon/codegenie@v${version}`); + } + }); + it("pins pull-request jobs to the base SHA and leaves comment jobs on the default branch", () => { const dogfood = parseYaml(readFileSync(path.resolve(workflowPaths[0] ?? ""), "utf8")) as WorkflowDocument; const prExample = parseYaml(readFileSync(path.resolve(workflowPaths[2] ?? ""), "utf8")) as WorkflowDocument; diff --git a/tests/package-build.test.ts b/tests/package-build.test.ts index 6bb19f3..a6e34f1 100644 --- a/tests/package-build.test.ts +++ b/tests/package-build.test.ts @@ -5,6 +5,9 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; import { parse as parseYaml } from "yaml"; import { renderVersion } from "../src/cli/version.js"; +import { GRAMMAR_IDS, GRAMMAR_WASM } from "../src/repo/tree-sitter/tree-sitter-service.js"; + +type DependencyTree = Record; const APPROVED_DEPENDENCY_BUILDS = ["esbuild"]; const DENIED_DEPENDENCY_BUILDS = [ @@ -25,7 +28,9 @@ describe("package build scaffold", () => { const buildConfig = JSON.parse(readFileSync("tsconfig.build.json", "utf8")); expect(packageJson.bin.codegenie).toBe("./dist/cli/main.js"); - expect(packageJson.scripts.build).toBe("tsc -p tsconfig.build.json && node scripts/write-version.mjs"); + expect(packageJson.scripts.build).toBe( + "tsc -p tsconfig.build.json && node scripts/write-version.mjs && node scripts/copy-grammars.mjs" + ); expect(buildConfig.compilerOptions.rootDir).toBe("src"); expect(buildConfig.compilerOptions.outDir).toBe("dist"); expect(buildConfig.include).toEqual(["src/**/*.ts"]); @@ -52,10 +57,20 @@ describe("package build scaffold", () => { ]; for (const [packageName, version, wasm] of grammars) { - expect(packageJson.dependencies[packageName]).toBe(version); + // Grammar packages are native-build packages, so they stay out of the + // installed dependency tree: the build vendors their WASM instead. + expect(packageJson.devDependencies[packageName]).toBe(version); + expect(packageJson.dependencies[packageName]).toBeUndefined(); expect(workspacePolicy).toContain(` - ${packageName}`); expect(existsSync(`node_modules/${packageName}/${wasm}`)).toBe(true); } + // Every routed grammar must be vendorable from a declared devDependency, + // so scripts/copy-grammars.mjs cannot silently drop one. + for (const grammarId of GRAMMAR_IDS) { + const { package: packageName, file } = GRAMMAR_WASM[grammarId]; + expect(packageJson.devDependencies[packageName]).toBeDefined(); + expect(existsSync(`node_modules/${packageName}/${file}`)).toBe(true); + } expect(workspacePolicy).toContain("peerDependencyRules:"); expect(workspacePolicy).toContain(" ignoreMissing:\n - tree-sitter"); expect(parsedPolicy.allowBuilds).toEqual(Object.fromEntries([ @@ -66,6 +81,73 @@ describe("package build scaffold", () => { expect(parsedPolicy.ignoredBuiltDependencies).toEqual(DENIED_DEPENDENCY_BUILDS); }); + // `npm install -g @0xsequence/codegenie` must never need a C++ toolchain. It + // once did: tree-sitter-solidity misspells its optional-peer key as + // `tree_sitter`, so npm read `tree-sitter` as a required peer, installed the + // native package, and compiled it from source — which fails against Node >= 23 + // V8 headers. pnpm's install policy hid that from this repo, so assert the + // shipped closure directly instead of trusting the policy. + it("ships a dependency closure that needs no native toolchain", () => { + const listed = JSON.parse(execFileSync("pnpm", [ + "list", + "--prod", + "--depth", + "Infinity", + "--json" + ], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] })) as Array<{ + dependencies?: DependencyTree; + }>; + const closure = new Map(); + const collect = (dependencies: DependencyTree | undefined): void => { + for (const [name, node] of Object.entries(dependencies ?? {})) { + const id = `${name}@${node.version}`; + if (typeof node.path !== "string" || closure.has(id)) { + continue; + } + closure.set(id, node.path); + collect(node.dependencies); + } + }; + collect(listed[0]?.dependencies); + const installedNames = new Set([...closure.keys()].map((id) => id.slice(0, id.lastIndexOf("@")))); + + expect(closure.size).toBeGreaterThan(0); + expect(installedNames.has("web-tree-sitter")).toBe(true); + for (const nativeName of ["tree-sitter", "node-gyp-build", "node-addon-api", "yarn"]) { + expect(installedNames.has(nativeName)).toBe(false); + } + expect([...installedNames].filter((name) => name.startsWith("tree-sitter-"))).toEqual([]); + + const nativeBuilds: string[] = []; + const unsatisfiedPeers: string[] = []; + for (const [id, directory] of closure) { + const manifest = JSON.parse(readFileSync(path.join(directory, "package.json"), "utf8")) as { + scripts?: Record; + peerDependencies?: Record; + peerDependenciesMeta?: Record; + }; + for (const hook of ["preinstall", "install", "postinstall"]) { + const script = manifest.scripts?.[hook]; + if (script !== undefined && /node-gyp|node-pre-gyp|prebuild-install|cmake-js/u.test(script)) { + nativeBuilds.push(`${id}: ${hook}: ${script}`); + } + } + if (existsSync(path.join(directory, "binding.gyp"))) { + nativeBuilds.push(`${id}: ships binding.gyp`); + } + // A peer npm considers required gets installed whether we want it or not, + // so every declared peer must be correctly marked optional or already here. + for (const peer of Object.keys(manifest.peerDependencies ?? {})) { + if (manifest.peerDependenciesMeta?.[peer]?.optional !== true && !installedNames.has(peer)) { + unsatisfiedPeers.push(`${id} requires peer ${peer}`); + } + } + } + + expect(nativeBuilds).toEqual([]); + expect(unsatisfiedPeers).toEqual([]); + }); + it("loads all skills and grammars under an explicit consumer build-script policy", () => { const sandbox = mkdtempSync(path.join(tmpdir(), "codegenie-package-smoke-")); try { @@ -91,32 +173,10 @@ describe("package build scaffold", () => { "dist/repo/tree-sitter/python-adapter.js", "dist/repo/tree-sitter/rust-adapter.js", "dist/repo/tree-sitter/solidity-adapter.js", - "package.json" + "package.json", + ...GRAMMAR_IDS.map((grammarId) => `bundled-grammars/${GRAMMAR_WASM[grammarId].file}`) ])); const tarballPath = path.join(packageDirectory, artifact!.filename); - const parserPackages = new Set([ - "tree-sitter-go", - "tree-sitter-javascript", - "tree-sitter-python", - "tree-sitter-rust", - "tree-sitter-solidity", - "tree-sitter-typescript", - "web-tree-sitter" - ]); - const parserTarballs = new Map([...parserPackages].map((packageName) => { - // These are already-published package contents. Repacking them must not run - // source-repository prepack hooks; the consumer install below still runs - // dependency lifecycle scripts under its explicit allowBuilds policy. - const dependencyPack = JSON.parse(execFileSync("npm", [ - "pack", - "--ignore-scripts", - path.resolve(`node_modules/${packageName}`), - "--json", - "--pack-destination", - packageDirectory - ], { encoding: "utf8" })) as Array<{ filename: string }>; - return [packageName, path.join(packageDirectory, dependencyPack[0]!.filename)] as const; - })); writeFileSync(path.join(consumerDirectory, "package.json"), JSON.stringify({ private: true, type: "module", @@ -126,19 +186,13 @@ describe("package build scaffold", () => { })); const packageJson = JSON.parse(readFileSync("package.json", "utf8")) as { dependencies: Record }; const locallyLinkedDependencies = Object.keys(packageJson.dependencies) - .filter((name) => !parserPackages.has(name)) .sort() .map((name) => ` '${name}': 'link:${path.resolve(`node_modules/${name}`)}'`); - const parserOverrides = [...parserTarballs] - .map(([name, tarball]) => ` '${name}': 'file:${tarball}'`); - // Keep unrelated product dependencies offline while installing packed parser dependencies into the consumer. + // Keeps the consumer install offline. Every product dependency is pure + // JavaScript now, so nothing here needs a packed native tarball. const consumerWorkspacePolicy = [ "overrides:", ...locallyLinkedDependencies, - ...parserOverrides, - ` 'node-addon-api': 'link:${path.resolve("node_modules/node-addon-api")}'`, - ` 'node-gyp-build': 'link:${path.resolve("node_modules/node-gyp-build")}'`, - ` 'yarn': 'link:${path.resolve("node_modules/yarn")}'`, "onlyBuiltDependencies:", ...APPROVED_DEPENDENCY_BUILDS.map((name) => ` - '${name}'`), "ignoredBuiltDependencies:", @@ -210,6 +264,9 @@ describe("package build scaffold", () => { cwd: consumerDirectory, stdio: "pipe" }); + // The native tree-sitter binding is what broke `npm install -g` on Node 26. + // An installed codegenie must not pull it in through any path. + expect(existsSync(path.join(consumerDirectory, "node_modules", "tree-sitter"))).toBe(false); const smokePath = path.join(consumerDirectory, "smoke.mjs"); writeFileSync(smokePath, ` @@ -240,13 +297,14 @@ if (!javascript) throw new Error("installed JavaScript skill is missing"); const serviceUrl = import.meta.resolve("@0xsequence/codegenie/dist/repo/tree-sitter/tree-sitter-service.js"); const installedRequire = createRequire(serviceUrl); -const goGrammarPath = installedRequire.resolve("tree-sitter-go/tree-sitter-go.wasm"); -const typescriptGrammarPath = installedRequire.resolve("tree-sitter-typescript/tree-sitter-typescript.wasm"); -const tsxGrammarPath = installedRequire.resolve("tree-sitter-typescript/tree-sitter-tsx.wasm"); -const javascriptGrammarPath = installedRequire.resolve("tree-sitter-javascript/tree-sitter-javascript.wasm"); -const rustGrammarPath = installedRequire.resolve("tree-sitter-rust/tree-sitter-rust.wasm"); -const pythonGrammarPath = installedRequire.resolve("tree-sitter-python/tree-sitter-python.wasm"); -const solidityGrammarPath = installedRequire.resolve("tree-sitter-solidity/tree-sitter-solidity.wasm"); +const bundled = (file) => installedRequire.resolve("@0xsequence/codegenie/bundled-grammars/" + file); +const goGrammarPath = bundled("tree-sitter-go.wasm"); +const typescriptGrammarPath = bundled("tree-sitter-typescript.wasm"); +const tsxGrammarPath = bundled("tree-sitter-tsx.wasm"); +const javascriptGrammarPath = bundled("tree-sitter-javascript.wasm"); +const rustGrammarPath = bundled("tree-sitter-rust.wasm"); +const pythonGrammarPath = bundled("tree-sitter-python.wasm"); +const solidityGrammarPath = bundled("tree-sitter-solidity.wasm"); const service = new TreeSitterService(); const grammarInputs = [ ["go", "package fixture\\nfunc value() int { return 1 }"],