feat(core): enable Node.js runtime alongside Bun#16825
Conversation
Greptile SummaryThis PR replaces the ad-hoc Bun-based package installation ( Key changes:
Issues found:
Confidence Score: 2/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant LSP as LSPServer / Plugin / Provider
participant Npm as Npm module
participant Arb as @npmcli/arborist
participant FS as Filesystem (.bin)
LSP->>Npm: which(pkg)
Npm->>FS: readdir(cache/packages/<pkg>/node_modules/.bin)
alt .bin exists and non-empty
FS-->>Npm: [files]
Npm-->>LSP: path to files[0]
else .bin empty or missing
FS-->>Npm: []
Npm->>Npm: add(pkg)
Npm->>Arb: new Arborist({ path: cache/packages/<pkg> })
Arb->>Arb: loadVirtual()
alt already installed (virtual tree exists)
Arb-->>Npm: tree with edgesOut
Npm-->>LSP: first.path (skips reify)
else not installed
Arb->>Arb: reify({ add: [pkg] })
Arb-->>Npm: result tree
Npm-->>Npm: return first.path
end
Npm->>Npm: which(pkg) [recursive call]
Npm->>FS: readdir(...)
FS-->>Npm: [files] (or [] → infinite recursion risk)
Npm-->>LSP: path to files[0]
end
Last reviewed commit: 64b5e70 |
| export async function which(pkg: string) { | ||
| const dir = path.join(directory(pkg), "node_modules", ".bin") | ||
| const files = await readdir(dir).catch(() => []) | ||
| if (!files.length) { | ||
| await add(pkg) | ||
| return which(pkg) | ||
| } | ||
| return path.join(dir, files[0]) | ||
| } |
There was a problem hiding this comment.
Unbounded infinite recursion when package has no bin entries
which calls add(pkg), then unconditionally recurses via return which(pkg). If the package installs successfully but exposes zero bin entries (no bin field in package.json, or binLinks fails to create symlinks), readdir returns [] again and the function recurses forever, eventually crashing with a stack overflow.
A base-case guard is needed after the install attempt:
| export async function which(pkg: string) { | |
| const dir = path.join(directory(pkg), "node_modules", ".bin") | |
| const files = await readdir(dir).catch(() => []) | |
| if (!files.length) { | |
| await add(pkg) | |
| return which(pkg) | |
| } | |
| return path.join(dir, files[0]) | |
| } | |
| export async function which(pkg: string) { | |
| const dir = path.join(directory(pkg), "node_modules", ".bin") | |
| const files = await readdir(dir).catch(() => []) | |
| if (!files.length) { | |
| await add(pkg) | |
| const filesAfterInstall = await readdir(dir).catch(() => []) | |
| if (!filesAfterInstall.length) throw new InstallFailedError({ pkg }) | |
| return path.join(dir, filesAfterInstall[0]) | |
| } | |
| return path.join(dir, files[0]) | |
| } |
| await add(pkg) | ||
| return which(pkg) | ||
| } | ||
| return path.join(dir, files[0]) |
There was a problem hiding this comment.
which picks first alphabetical binary — wrong for multi-binary packages
readdir returns directory entries in filesystem order (alphabetical on most systems). Packages like @astrojs/language-server (exposes astro-ls) or @vue/language-server (exposes vue-language-server) may also install helper binaries. Taking files[0] silently picks whichever name sorts first, which is not guaranteed to be the main entrypoint binary.
For example, if @astrojs/language-server installs astro-check and astro-ls into .bin, files[0] would be astro-check, not astro-ls.
Consider accepting an explicit binary name as a parameter, or looking up the bin field from the installed package's package.json.
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Formatters now determine their executable location when enabled rather than using hardcoded paths. This ensures formatters work correctly regardless of how the tool was installed or where executables are located on the system.
…r in CI by disabling Bun cache when network interception is detected
…xecutables to work while keeping them disabled on Windows CI where symlink permissions are restricted
…links when symlink permissions are restricted
59a7d8d to
7478e07
Compare
Enable running opencode on Node.js by adding platform-specific database adapters and replacing Bun-specific shell execution with cross-platform Process utility.
Plugins no longer receive shell access or server URL to prevent unauthorized execution and limit plugin sandbox surface area.
…erver - Enables graceful server shutdown for workspace management - Removes unsupported serverUrl getter that threw errors in plugin context
- Removed debug console.log when dependency installation fails so users see clean warning messages instead of raw error dumps - Fixed database connection cleanup to prevent resource leaks between sessions - Added support for loading custom tools from both .opencode/tool (singular) and .opencode/tools (plural) directories, matching common naming conventions
Summary
Refactors and improvements to the LSP server and core components.
Changes