refactor: migrate src/lsp/server.ts from Bun.file()/Bun.write() to Filesystem module (#14138)
This commit is contained in:
@@ -147,8 +147,7 @@ export namespace LSPClient {
|
|||||||
notify: {
|
notify: {
|
||||||
async open(input: { path: string }) {
|
async open(input: { path: string }) {
|
||||||
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path)
|
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path)
|
||||||
const file = Bun.file(input.path)
|
const text = await Filesystem.readText(input.path)
|
||||||
const text = await file.text()
|
|
||||||
const extension = path.extname(input.path)
|
const extension = path.extname(input.path)
|
||||||
const languageId = LANGUAGE_EXTENSIONS[extension] ?? "plaintext"
|
const languageId = LANGUAGE_EXTENSIONS[extension] ?? "plaintext"
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ export namespace LSPServer {
|
|||||||
"bin",
|
"bin",
|
||||||
"vue-language-server.js",
|
"vue-language-server.js",
|
||||||
)
|
)
|
||||||
if (!(await Bun.file(js).exists())) {
|
if (!(await Filesystem.exists(js))) {
|
||||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
await Bun.spawn([BunProc.which(), "install", "@vue/language-server"], {
|
await Bun.spawn([BunProc.which(), "install", "@vue/language-server"], {
|
||||||
cwd: Global.Path.bin,
|
cwd: Global.Path.bin,
|
||||||
@@ -173,14 +173,14 @@ export namespace LSPServer {
|
|||||||
if (!eslint) return
|
if (!eslint) return
|
||||||
log.info("spawning eslint server")
|
log.info("spawning eslint server")
|
||||||
const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js")
|
const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js")
|
||||||
if (!(await Bun.file(serverPath).exists())) {
|
if (!(await Filesystem.exists(serverPath))) {
|
||||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
log.info("downloading and building VS Code ESLint server")
|
log.info("downloading and building VS Code ESLint server")
|
||||||
const response = await fetch("https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip")
|
const response = await fetch("https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip")
|
||||||
if (!response.ok) return
|
if (!response.ok) return
|
||||||
|
|
||||||
const zipPath = path.join(Global.Path.bin, "vscode-eslint.zip")
|
const zipPath = path.join(Global.Path.bin, "vscode-eslint.zip")
|
||||||
await Bun.file(zipPath).write(response)
|
if (response.body) await Filesystem.writeStream(zipPath, response.body)
|
||||||
|
|
||||||
const ok = await Archive.extractZip(zipPath, Global.Path.bin)
|
const ok = await Archive.extractZip(zipPath, Global.Path.bin)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
@@ -242,7 +242,7 @@ export namespace LSPServer {
|
|||||||
|
|
||||||
const resolveBin = async (target: string) => {
|
const resolveBin = async (target: string) => {
|
||||||
const localBin = path.join(root, target)
|
const localBin = path.join(root, target)
|
||||||
if (await Bun.file(localBin).exists()) return localBin
|
if (await Filesystem.exists(localBin)) return localBin
|
||||||
|
|
||||||
const candidates = Filesystem.up({
|
const candidates = Filesystem.up({
|
||||||
targets: [target],
|
targets: [target],
|
||||||
@@ -326,7 +326,7 @@ export namespace LSPServer {
|
|||||||
async spawn(root) {
|
async spawn(root) {
|
||||||
const localBin = path.join(root, "node_modules", ".bin", "biome")
|
const localBin = path.join(root, "node_modules", ".bin", "biome")
|
||||||
let bin: string | undefined
|
let bin: string | undefined
|
||||||
if (await Bun.file(localBin).exists()) bin = localBin
|
if (await Filesystem.exists(localBin)) bin = localBin
|
||||||
if (!bin) {
|
if (!bin) {
|
||||||
const found = Bun.which("biome")
|
const found = Bun.which("biome")
|
||||||
if (found) bin = found
|
if (found) bin = found
|
||||||
@@ -467,7 +467,7 @@ export namespace LSPServer {
|
|||||||
const potentialPythonPath = isWindows
|
const potentialPythonPath = isWindows
|
||||||
? path.join(venvPath, "Scripts", "python.exe")
|
? path.join(venvPath, "Scripts", "python.exe")
|
||||||
: path.join(venvPath, "bin", "python")
|
: path.join(venvPath, "bin", "python")
|
||||||
if (await Bun.file(potentialPythonPath).exists()) {
|
if (await Filesystem.exists(potentialPythonPath)) {
|
||||||
initialization["pythonPath"] = potentialPythonPath
|
initialization["pythonPath"] = potentialPythonPath
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -479,7 +479,7 @@ export namespace LSPServer {
|
|||||||
const potentialTyPath = isWindows
|
const potentialTyPath = isWindows
|
||||||
? path.join(venvPath, "Scripts", "ty.exe")
|
? path.join(venvPath, "Scripts", "ty.exe")
|
||||||
: path.join(venvPath, "bin", "ty")
|
: path.join(venvPath, "bin", "ty")
|
||||||
if (await Bun.file(potentialTyPath).exists()) {
|
if (await Filesystem.exists(potentialTyPath)) {
|
||||||
binary = potentialTyPath
|
binary = potentialTyPath
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -511,7 +511,7 @@ export namespace LSPServer {
|
|||||||
const args = []
|
const args = []
|
||||||
if (!binary) {
|
if (!binary) {
|
||||||
const js = path.join(Global.Path.bin, "node_modules", "pyright", "dist", "pyright-langserver.js")
|
const js = path.join(Global.Path.bin, "node_modules", "pyright", "dist", "pyright-langserver.js")
|
||||||
if (!(await Bun.file(js).exists())) {
|
if (!(await Filesystem.exists(js))) {
|
||||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
await Bun.spawn([BunProc.which(), "install", "pyright"], {
|
await Bun.spawn([BunProc.which(), "install", "pyright"], {
|
||||||
cwd: Global.Path.bin,
|
cwd: Global.Path.bin,
|
||||||
@@ -536,7 +536,7 @@ export namespace LSPServer {
|
|||||||
const potentialPythonPath = isWindows
|
const potentialPythonPath = isWindows
|
||||||
? path.join(venvPath, "Scripts", "python.exe")
|
? path.join(venvPath, "Scripts", "python.exe")
|
||||||
: path.join(venvPath, "bin", "python")
|
: path.join(venvPath, "bin", "python")
|
||||||
if (await Bun.file(potentialPythonPath).exists()) {
|
if (await Filesystem.exists(potentialPythonPath)) {
|
||||||
initialization["pythonPath"] = potentialPythonPath
|
initialization["pythonPath"] = potentialPythonPath
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -571,7 +571,7 @@ export namespace LSPServer {
|
|||||||
process.platform === "win32" ? "language_server.bat" : "language_server.sh",
|
process.platform === "win32" ? "language_server.bat" : "language_server.sh",
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!(await Bun.file(binary).exists())) {
|
if (!(await Filesystem.exists(binary))) {
|
||||||
const elixir = Bun.which("elixir")
|
const elixir = Bun.which("elixir")
|
||||||
if (!elixir) {
|
if (!elixir) {
|
||||||
log.error("elixir is required to run elixir-ls")
|
log.error("elixir is required to run elixir-ls")
|
||||||
@@ -584,7 +584,7 @@ export namespace LSPServer {
|
|||||||
const response = await fetch("https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip")
|
const response = await fetch("https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip")
|
||||||
if (!response.ok) return
|
if (!response.ok) return
|
||||||
const zipPath = path.join(Global.Path.bin, "elixir-ls.zip")
|
const zipPath = path.join(Global.Path.bin, "elixir-ls.zip")
|
||||||
await Bun.file(zipPath).write(response)
|
if (response.body) await Filesystem.writeStream(zipPath, response.body)
|
||||||
|
|
||||||
const ok = await Archive.extractZip(zipPath, Global.Path.bin)
|
const ok = await Archive.extractZip(zipPath, Global.Path.bin)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
@@ -692,7 +692,7 @@ export namespace LSPServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tempPath = path.join(Global.Path.bin, assetName)
|
const tempPath = path.join(Global.Path.bin, assetName)
|
||||||
await Bun.file(tempPath).write(downloadResponse)
|
if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body)
|
||||||
|
|
||||||
if (ext === "zip") {
|
if (ext === "zip") {
|
||||||
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
||||||
@@ -710,7 +710,7 @@ export namespace LSPServer {
|
|||||||
|
|
||||||
bin = path.join(Global.Path.bin, "zls" + (platform === "win32" ? ".exe" : ""))
|
bin = path.join(Global.Path.bin, "zls" + (platform === "win32" ? ".exe" : ""))
|
||||||
|
|
||||||
if (!(await Bun.file(bin).exists())) {
|
if (!(await Filesystem.exists(bin))) {
|
||||||
log.error("Failed to extract zls binary")
|
log.error("Failed to extract zls binary")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -857,7 +857,7 @@ export namespace LSPServer {
|
|||||||
// Stop at filesystem root
|
// Stop at filesystem root
|
||||||
const cargoTomlPath = path.join(currentDir, "Cargo.toml")
|
const cargoTomlPath = path.join(currentDir, "Cargo.toml")
|
||||||
try {
|
try {
|
||||||
const cargoTomlContent = await Bun.file(cargoTomlPath).text()
|
const cargoTomlContent = await Filesystem.readText(cargoTomlPath)
|
||||||
if (cargoTomlContent.includes("[workspace]")) {
|
if (cargoTomlContent.includes("[workspace]")) {
|
||||||
return currentDir
|
return currentDir
|
||||||
}
|
}
|
||||||
@@ -907,7 +907,7 @@ export namespace LSPServer {
|
|||||||
|
|
||||||
const ext = process.platform === "win32" ? ".exe" : ""
|
const ext = process.platform === "win32" ? ".exe" : ""
|
||||||
const direct = path.join(Global.Path.bin, "clangd" + ext)
|
const direct = path.join(Global.Path.bin, "clangd" + ext)
|
||||||
if (await Bun.file(direct).exists()) {
|
if (await Filesystem.exists(direct)) {
|
||||||
return {
|
return {
|
||||||
process: spawn(direct, args, {
|
process: spawn(direct, args, {
|
||||||
cwd: root,
|
cwd: root,
|
||||||
@@ -920,7 +920,7 @@ export namespace LSPServer {
|
|||||||
if (!entry.isDirectory()) continue
|
if (!entry.isDirectory()) continue
|
||||||
if (!entry.name.startsWith("clangd_")) continue
|
if (!entry.name.startsWith("clangd_")) continue
|
||||||
const candidate = path.join(Global.Path.bin, entry.name, "bin", "clangd" + ext)
|
const candidate = path.join(Global.Path.bin, entry.name, "bin", "clangd" + ext)
|
||||||
if (await Bun.file(candidate).exists()) {
|
if (await Filesystem.exists(candidate)) {
|
||||||
return {
|
return {
|
||||||
process: spawn(candidate, args, {
|
process: spawn(candidate, args, {
|
||||||
cwd: root,
|
cwd: root,
|
||||||
@@ -990,7 +990,7 @@ export namespace LSPServer {
|
|||||||
log.error("Failed to write clangd archive")
|
log.error("Failed to write clangd archive")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await Bun.write(archive, buf)
|
await Filesystem.write(archive, Buffer.from(buf))
|
||||||
|
|
||||||
const zip = name.endsWith(".zip")
|
const zip = name.endsWith(".zip")
|
||||||
const tar = name.endsWith(".tar.xz")
|
const tar = name.endsWith(".tar.xz")
|
||||||
@@ -1014,7 +1014,7 @@ export namespace LSPServer {
|
|||||||
await fs.rm(archive, { force: true })
|
await fs.rm(archive, { force: true })
|
||||||
|
|
||||||
const bin = path.join(Global.Path.bin, "clangd_" + tag, "bin", "clangd" + ext)
|
const bin = path.join(Global.Path.bin, "clangd_" + tag, "bin", "clangd" + ext)
|
||||||
if (!(await Bun.file(bin).exists())) {
|
if (!(await Filesystem.exists(bin))) {
|
||||||
log.error("Failed to extract clangd binary")
|
log.error("Failed to extract clangd binary")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1045,7 +1045,7 @@ export namespace LSPServer {
|
|||||||
const args: string[] = []
|
const args: string[] = []
|
||||||
if (!binary) {
|
if (!binary) {
|
||||||
const js = path.join(Global.Path.bin, "node_modules", "svelte-language-server", "bin", "server.js")
|
const js = path.join(Global.Path.bin, "node_modules", "svelte-language-server", "bin", "server.js")
|
||||||
if (!(await Bun.file(js).exists())) {
|
if (!(await Filesystem.exists(js))) {
|
||||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
await Bun.spawn([BunProc.which(), "install", "svelte-language-server"], {
|
await Bun.spawn([BunProc.which(), "install", "svelte-language-server"], {
|
||||||
cwd: Global.Path.bin,
|
cwd: Global.Path.bin,
|
||||||
@@ -1092,7 +1092,7 @@ export namespace LSPServer {
|
|||||||
const args: string[] = []
|
const args: string[] = []
|
||||||
if (!binary) {
|
if (!binary) {
|
||||||
const js = path.join(Global.Path.bin, "node_modules", "@astrojs", "language-server", "bin", "nodeServer.js")
|
const js = path.join(Global.Path.bin, "node_modules", "@astrojs", "language-server", "bin", "nodeServer.js")
|
||||||
if (!(await Bun.file(js).exists())) {
|
if (!(await Filesystem.exists(js))) {
|
||||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
await Bun.spawn([BunProc.which(), "install", "@astrojs/language-server"], {
|
await Bun.spawn([BunProc.which(), "install", "@astrojs/language-server"], {
|
||||||
cwd: Global.Path.bin,
|
cwd: Global.Path.bin,
|
||||||
@@ -1248,7 +1248,7 @@ export namespace LSPServer {
|
|||||||
const distPath = path.join(Global.Path.bin, "kotlin-ls")
|
const distPath = path.join(Global.Path.bin, "kotlin-ls")
|
||||||
const launcherScript =
|
const launcherScript =
|
||||||
process.platform === "win32" ? path.join(distPath, "kotlin-lsp.cmd") : path.join(distPath, "kotlin-lsp.sh")
|
process.platform === "win32" ? path.join(distPath, "kotlin-lsp.cmd") : path.join(distPath, "kotlin-lsp.sh")
|
||||||
const installed = await Bun.file(launcherScript).exists()
|
const installed = await Filesystem.exists(launcherScript)
|
||||||
if (!installed) {
|
if (!installed) {
|
||||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
log.info("Downloading Kotlin Language Server from GitHub.")
|
log.info("Downloading Kotlin Language Server from GitHub.")
|
||||||
@@ -1307,7 +1307,7 @@ export namespace LSPServer {
|
|||||||
}
|
}
|
||||||
log.info("Installed Kotlin Language Server", { path: launcherScript })
|
log.info("Installed Kotlin Language Server", { path: launcherScript })
|
||||||
}
|
}
|
||||||
if (!(await Bun.file(launcherScript).exists())) {
|
if (!(await Filesystem.exists(launcherScript))) {
|
||||||
log.error(`Failed to locate the Kotlin LS launcher script in the installed directory: ${distPath}.`)
|
log.error(`Failed to locate the Kotlin LS launcher script in the installed directory: ${distPath}.`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1336,7 +1336,7 @@ export namespace LSPServer {
|
|||||||
"src",
|
"src",
|
||||||
"server.js",
|
"server.js",
|
||||||
)
|
)
|
||||||
const exists = await Bun.file(js).exists()
|
const exists = await Filesystem.exists(js)
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
await Bun.spawn([BunProc.which(), "install", "yaml-language-server"], {
|
await Bun.spawn([BunProc.which(), "install", "yaml-language-server"], {
|
||||||
@@ -1443,7 +1443,7 @@ export namespace LSPServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tempPath = path.join(Global.Path.bin, assetName)
|
const tempPath = path.join(Global.Path.bin, assetName)
|
||||||
await Bun.file(tempPath).write(downloadResponse)
|
if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body)
|
||||||
|
|
||||||
// Unlike zls which is a single self-contained binary,
|
// Unlike zls which is a single self-contained binary,
|
||||||
// lua-language-server needs supporting files (meta/, locale/, etc.)
|
// lua-language-server needs supporting files (meta/, locale/, etc.)
|
||||||
@@ -1482,7 +1482,7 @@ export namespace LSPServer {
|
|||||||
// Binary is located in bin/ subdirectory within the extracted archive
|
// Binary is located in bin/ subdirectory within the extracted archive
|
||||||
bin = path.join(installDir, "bin", "lua-language-server" + (platform === "win32" ? ".exe" : ""))
|
bin = path.join(installDir, "bin", "lua-language-server" + (platform === "win32" ? ".exe" : ""))
|
||||||
|
|
||||||
if (!(await Bun.file(bin).exists())) {
|
if (!(await Filesystem.exists(bin))) {
|
||||||
log.error("Failed to extract lua-language-server binary")
|
log.error("Failed to extract lua-language-server binary")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1516,7 +1516,7 @@ export namespace LSPServer {
|
|||||||
const args: string[] = []
|
const args: string[] = []
|
||||||
if (!binary) {
|
if (!binary) {
|
||||||
const js = path.join(Global.Path.bin, "node_modules", "intelephense", "lib", "intelephense.js")
|
const js = path.join(Global.Path.bin, "node_modules", "intelephense", "lib", "intelephense.js")
|
||||||
if (!(await Bun.file(js).exists())) {
|
if (!(await Filesystem.exists(js))) {
|
||||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
await Bun.spawn([BunProc.which(), "install", "intelephense"], {
|
await Bun.spawn([BunProc.which(), "install", "intelephense"], {
|
||||||
cwd: Global.Path.bin,
|
cwd: Global.Path.bin,
|
||||||
@@ -1613,7 +1613,7 @@ export namespace LSPServer {
|
|||||||
const args: string[] = []
|
const args: string[] = []
|
||||||
if (!binary) {
|
if (!binary) {
|
||||||
const js = path.join(Global.Path.bin, "node_modules", "bash-language-server", "out", "cli.js")
|
const js = path.join(Global.Path.bin, "node_modules", "bash-language-server", "out", "cli.js")
|
||||||
if (!(await Bun.file(js).exists())) {
|
if (!(await Filesystem.exists(js))) {
|
||||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
await Bun.spawn([BunProc.which(), "install", "bash-language-server"], {
|
await Bun.spawn([BunProc.which(), "install", "bash-language-server"], {
|
||||||
cwd: Global.Path.bin,
|
cwd: Global.Path.bin,
|
||||||
@@ -1694,7 +1694,7 @@ export namespace LSPServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tempPath = path.join(Global.Path.bin, assetName)
|
const tempPath = path.join(Global.Path.bin, assetName)
|
||||||
await Bun.file(tempPath).write(downloadResponse)
|
if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body)
|
||||||
|
|
||||||
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
@@ -1707,7 +1707,7 @@ export namespace LSPServer {
|
|||||||
|
|
||||||
bin = path.join(Global.Path.bin, "terraform-ls" + (platform === "win32" ? ".exe" : ""))
|
bin = path.join(Global.Path.bin, "terraform-ls" + (platform === "win32" ? ".exe" : ""))
|
||||||
|
|
||||||
if (!(await Bun.file(bin).exists())) {
|
if (!(await Filesystem.exists(bin))) {
|
||||||
log.error("Failed to extract terraform-ls binary")
|
log.error("Failed to extract terraform-ls binary")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1784,7 +1784,7 @@ export namespace LSPServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tempPath = path.join(Global.Path.bin, assetName)
|
const tempPath = path.join(Global.Path.bin, assetName)
|
||||||
await Bun.file(tempPath).write(downloadResponse)
|
if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body)
|
||||||
|
|
||||||
if (ext === "zip") {
|
if (ext === "zip") {
|
||||||
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
||||||
@@ -1803,7 +1803,7 @@ export namespace LSPServer {
|
|||||||
|
|
||||||
bin = path.join(Global.Path.bin, "texlab" + (platform === "win32" ? ".exe" : ""))
|
bin = path.join(Global.Path.bin, "texlab" + (platform === "win32" ? ".exe" : ""))
|
||||||
|
|
||||||
if (!(await Bun.file(bin).exists())) {
|
if (!(await Filesystem.exists(bin))) {
|
||||||
log.error("Failed to extract texlab binary")
|
log.error("Failed to extract texlab binary")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1832,7 +1832,7 @@ export namespace LSPServer {
|
|||||||
const args: string[] = []
|
const args: string[] = []
|
||||||
if (!binary) {
|
if (!binary) {
|
||||||
const js = path.join(Global.Path.bin, "node_modules", "dockerfile-language-server-nodejs", "lib", "server.js")
|
const js = path.join(Global.Path.bin, "node_modules", "dockerfile-language-server-nodejs", "lib", "server.js")
|
||||||
if (!(await Bun.file(js).exists())) {
|
if (!(await Filesystem.exists(js))) {
|
||||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||||
await Bun.spawn([BunProc.which(), "install", "dockerfile-language-server-nodejs"], {
|
await Bun.spawn([BunProc.which(), "install", "dockerfile-language-server-nodejs"], {
|
||||||
cwd: Global.Path.bin,
|
cwd: Global.Path.bin,
|
||||||
@@ -1990,7 +1990,7 @@ export namespace LSPServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tempPath = path.join(Global.Path.bin, assetName)
|
const tempPath = path.join(Global.Path.bin, assetName)
|
||||||
await Bun.file(tempPath).write(downloadResponse)
|
if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body)
|
||||||
|
|
||||||
if (ext === "zip") {
|
if (ext === "zip") {
|
||||||
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
||||||
@@ -2008,7 +2008,7 @@ export namespace LSPServer {
|
|||||||
|
|
||||||
bin = path.join(Global.Path.bin, "tinymist" + (platform === "win32" ? ".exe" : ""))
|
bin = path.join(Global.Path.bin, "tinymist" + (platform === "win32" ? ".exe" : ""))
|
||||||
|
|
||||||
if (!(await Bun.file(bin).exists())) {
|
if (!(await Filesystem.exists(bin))) {
|
||||||
log.error("Failed to extract tinymist binary")
|
log.error("Failed to extract tinymist binary")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { mkdir, readFile, writeFile } from "fs/promises"
|
import { chmod, mkdir, readFile, writeFile } from "fs/promises"
|
||||||
import { existsSync, statSync } from "fs"
|
import { createWriteStream, existsSync, statSync } from "fs"
|
||||||
import { lookup } from "mime-types"
|
import { lookup } from "mime-types"
|
||||||
import { realpathSync } from "fs"
|
import { realpathSync } from "fs"
|
||||||
import { dirname, join, relative } from "path"
|
import { dirname, join, relative } from "path"
|
||||||
|
import { Readable } from "stream"
|
||||||
|
import { pipeline } from "stream/promises"
|
||||||
|
|
||||||
export namespace Filesystem {
|
export namespace Filesystem {
|
||||||
// Fast sync version for metadata checks
|
// Fast sync version for metadata checks
|
||||||
@@ -68,6 +70,25 @@ export namespace Filesystem {
|
|||||||
return write(p, JSON.stringify(data, null, 2), mode)
|
return write(p, JSON.stringify(data, null, 2), mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function writeStream(
|
||||||
|
p: string,
|
||||||
|
stream: ReadableStream<Uint8Array> | Readable,
|
||||||
|
mode?: number,
|
||||||
|
): Promise<void> {
|
||||||
|
const dir = dirname(p)
|
||||||
|
if (!existsSync(dir)) {
|
||||||
|
await mkdir(dir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeStream = stream instanceof ReadableStream ? Readable.fromWeb(stream as any) : stream
|
||||||
|
const writeStream = createWriteStream(p)
|
||||||
|
await pipeline(nodeStream, writeStream)
|
||||||
|
|
||||||
|
if (mode) {
|
||||||
|
await chmod(p, mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function mimeType(p: string): string {
|
export function mimeType(p: string): string {
|
||||||
return lookup(p) || "application/octet-stream"
|
return lookup(p) || "application/octet-stream"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,4 +285,125 @@ describe("filesystem", () => {
|
|||||||
expect(Filesystem.mimeType("Makefile")).toBe("application/octet-stream")
|
expect(Filesystem.mimeType("Makefile")).toBe("application/octet-stream")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("writeStream()", () => {
|
||||||
|
test("writes from Web ReadableStream", async () => {
|
||||||
|
await using tmp = await tmpdir()
|
||||||
|
const filepath = path.join(tmp.path, "streamed.txt")
|
||||||
|
const content = "Hello from stream!"
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
controller.enqueue(encoder.encode(content))
|
||||||
|
controller.close()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Filesystem.writeStream(filepath, stream)
|
||||||
|
|
||||||
|
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("writes from Node.js Readable stream", async () => {
|
||||||
|
await using tmp = await tmpdir()
|
||||||
|
const filepath = path.join(tmp.path, "node-streamed.txt")
|
||||||
|
const content = "Hello from Node stream!"
|
||||||
|
const { Readable } = await import("stream")
|
||||||
|
const stream = Readable.from([content])
|
||||||
|
|
||||||
|
await Filesystem.writeStream(filepath, stream)
|
||||||
|
|
||||||
|
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("writes binary data from Web ReadableStream", async () => {
|
||||||
|
await using tmp = await tmpdir()
|
||||||
|
const filepath = path.join(tmp.path, "binary.dat")
|
||||||
|
const binaryData = new Uint8Array([0x00, 0x01, 0x02, 0x03, 0xff])
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
controller.enqueue(binaryData)
|
||||||
|
controller.close()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Filesystem.writeStream(filepath, stream)
|
||||||
|
|
||||||
|
const read = await fs.readFile(filepath)
|
||||||
|
expect(Buffer.from(read)).toEqual(Buffer.from(binaryData))
|
||||||
|
})
|
||||||
|
|
||||||
|
test("writes large content in chunks", async () => {
|
||||||
|
await using tmp = await tmpdir()
|
||||||
|
const filepath = path.join(tmp.path, "large.txt")
|
||||||
|
const chunks = ["chunk1", "chunk2", "chunk3", "chunk4", "chunk5"]
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
controller.enqueue(new TextEncoder().encode(chunk))
|
||||||
|
}
|
||||||
|
controller.close()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Filesystem.writeStream(filepath, stream)
|
||||||
|
|
||||||
|
expect(await fs.readFile(filepath, "utf-8")).toBe(chunks.join(""))
|
||||||
|
})
|
||||||
|
|
||||||
|
test("creates parent directories", async () => {
|
||||||
|
await using tmp = await tmpdir()
|
||||||
|
const filepath = path.join(tmp.path, "nested", "deep", "streamed.txt")
|
||||||
|
const content = "nested stream content"
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
controller.enqueue(new TextEncoder().encode(content))
|
||||||
|
controller.close()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Filesystem.writeStream(filepath, stream)
|
||||||
|
|
||||||
|
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("writes with permissions", async () => {
|
||||||
|
await using tmp = await tmpdir()
|
||||||
|
const filepath = path.join(tmp.path, "protected-stream.txt")
|
||||||
|
const content = "secret stream content"
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
controller.enqueue(new TextEncoder().encode(content))
|
||||||
|
controller.close()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Filesystem.writeStream(filepath, stream, 0o600)
|
||||||
|
|
||||||
|
const stats = await fs.stat(filepath)
|
||||||
|
if (process.platform !== "win32") {
|
||||||
|
expect(stats.mode & 0o777).toBe(0o600)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("writes executable with permissions", async () => {
|
||||||
|
await using tmp = await tmpdir()
|
||||||
|
const filepath = path.join(tmp.path, "script.sh")
|
||||||
|
const content = "#!/bin/bash\necho hello"
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
controller.enqueue(new TextEncoder().encode(content))
|
||||||
|
controller.close()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Filesystem.writeStream(filepath, stream, 0o755)
|
||||||
|
|
||||||
|
const stats = await fs.stat(filepath)
|
||||||
|
if (process.platform !== "win32") {
|
||||||
|
expect(stats.mode & 0o777).toBe(0o755)
|
||||||
|
}
|
||||||
|
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user