Skip to content

Commit 01f0319

Browse files
authored
fix(lsp): MEMORY LEAK: ensure typescript server uses native project config (anomalyco#19953)
1 parent 517e6c9 commit 01f0319

3 files changed

Lines changed: 91 additions & 1 deletion

File tree

packages/opencode/src/lsp/server.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,17 @@ export namespace LSPServer {
105105
if (!tsserver) return
106106
const bin = await Npm.which("typescript-language-server")
107107
if (!bin) return
108-
const proc = spawn(bin, ["--stdio"], {
108+
109+
const args = ["--stdio", "--tsserver-log-verbosity", "off", "--tsserver-path", tsserver]
110+
111+
if (
112+
!(await pathExists(path.join(root, "tsconfig.json"))) &&
113+
!(await pathExists(path.join(root, "jsconfig.json")))
114+
) {
115+
args.push("--ignore-node-modules")
116+
}
117+
118+
const proc = spawn(bin, args, {
109119
cwd: root,
110120
env: {
111121
...process.env,

packages/opencode/test/lsp/index.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { describe, expect, spyOn, test } from "bun:test"
22
import path from "path"
3+
import fs from "fs/promises"
34
import * as Lsp from "../../src/lsp/index"
5+
import * as launch from "../../src/lsp/launch"
46
import { LSPServer } from "../../src/lsp/server"
57
import { Instance } from "../../src/project/instance"
68
import { tmpdir } from "../fixture/fixture"
@@ -52,4 +54,80 @@ describe("lsp.spawn", () => {
5254
await Instance.disposeAll()
5355
}
5456
})
57+
58+
test("spawns builtin Typescript LSP with correct arguments", async () => {
59+
await using tmp = await tmpdir()
60+
61+
// Create dummy tsserver to satisfy Module.resolve
62+
const tsdk = path.join(tmp.path, "node_modules", "typescript", "lib")
63+
await fs.mkdir(tsdk, { recursive: true })
64+
await fs.writeFile(path.join(tsdk, "tsserver.js"), "")
65+
66+
const spawnSpy = spyOn(launch, "spawn").mockImplementation(
67+
() =>
68+
({
69+
stdin: {},
70+
stdout: {},
71+
stderr: {},
72+
on: () => {},
73+
kill: () => {},
74+
}) as any,
75+
)
76+
77+
try {
78+
await Instance.provide({
79+
directory: tmp.path,
80+
fn: async () => {
81+
await LSPServer.Typescript.spawn(tmp.path)
82+
},
83+
})
84+
85+
expect(spawnSpy).toHaveBeenCalled()
86+
const args = spawnSpy.mock.calls[0][1] as string[]
87+
88+
expect(args).toContain("--tsserver-path")
89+
expect(args).toContain("--tsserver-log-verbosity")
90+
expect(args).toContain("off")
91+
} finally {
92+
spawnSpy.mockRestore()
93+
}
94+
})
95+
96+
test("spawns builtin Typescript LSP with --ignore-node-modules if no config is found", async () => {
97+
await using tmp = await tmpdir()
98+
99+
// Create dummy tsserver to satisfy Module.resolve
100+
const tsdk = path.join(tmp.path, "node_modules", "typescript", "lib")
101+
await fs.mkdir(tsdk, { recursive: true })
102+
await fs.writeFile(path.join(tsdk, "tsserver.js"), "")
103+
104+
// NO tsconfig.json or jsconfig.json created here
105+
106+
const spawnSpy = spyOn(launch, "spawn").mockImplementation(
107+
() =>
108+
({
109+
stdin: {},
110+
stdout: {},
111+
stderr: {},
112+
on: () => {},
113+
kill: () => {},
114+
}) as any,
115+
)
116+
117+
try {
118+
await Instance.provide({
119+
directory: tmp.path,
120+
fn: async () => {
121+
await LSPServer.Typescript.spawn(tmp.path)
122+
},
123+
})
124+
125+
expect(spawnSpy).toHaveBeenCalled()
126+
const args = spawnSpy.mock.calls[0][1] as string[]
127+
128+
expect(args).toContain("--ignore-node-modules")
129+
} finally {
130+
spawnSpy.mockRestore()
131+
}
132+
})
55133
})

packages/web/src/content/docs/ecosystem.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw
3232
| [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions for non-interactive shell commands - prevents hangs from TTY-dependent operations |
3333
| [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Track OpenCode usage with Wakatime |
3434
| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs |
35+
| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x faster code editing with Morph Fast Apply API and lazy edit markers |
3536
| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply editing, WarpGrep codebase search, and context compaction via Morph |
3637
| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible |
3738
| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop notifications and sound alerts for OpenCode sessions |
@@ -42,6 +43,7 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw
4243
| [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interactive plan review with visual annotation and private/offline sharing |
4344
| [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Extend opencode /commands into a powerful orchestration system with granular flow control |
4445
| [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Schedule recurring jobs using launchd (Mac) or systemd (Linux) with cron syntax |
46+
| [opencode-conductor](https://github.com/derekbar90/opencode-conductor) | Protocol-Driven Workflow: Automation of the Context -> Spec -> Plan -> Implement lifecycle. |
4547
| [micode](https://github.com/vtemian/micode) | Structured Brainstorm → Plan → Implement workflow with session continuity |
4648
| [octto](https://github.com/vtemian/octto) | Interactive browser UI for AI brainstorming with multi-question forms |
4749
| [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code-style background agents with async delegation and context persistence |

0 commit comments

Comments
 (0)