From 3d189b42a3bdd98675a972524389399d229d96a3 Mon Sep 17 00:00:00 2001 From: Dax Date: Wed, 18 Feb 2026 12:10:42 -0500 Subject: [PATCH] refactor: migrate file/ripgrep.ts from Bun.file()/Bun.write() to Filesystem module (#14159) --- packages/opencode/src/cli/cmd/mcp.ts | 11 ++-- packages/opencode/src/config/config.ts | 76 ++++++++++++-------------- packages/opencode/src/file/ripgrep.ts | 12 ++-- 3 files changed, 46 insertions(+), 53 deletions(-) diff --git a/packages/opencode/src/cli/cmd/mcp.ts b/packages/opencode/src/cli/cmd/mcp.ts index 95719215e..c45b9e55d 100644 --- a/packages/opencode/src/cli/cmd/mcp.ts +++ b/packages/opencode/src/cli/cmd/mcp.ts @@ -13,6 +13,7 @@ import { Installation } from "../../installation" import path from "path" import { Global } from "../../global" import { modify, applyEdits } from "jsonc-parser" +import { Filesystem } from "../../util/filesystem" import { Bus } from "../../bus" function getAuthStatusIcon(status: MCP.AuthStatus): string { @@ -388,7 +389,7 @@ async function resolveConfigPath(baseDir: string, global = false) { } for (const candidate of candidates) { - if (await Bun.file(candidate).exists()) { + if (await Filesystem.exists(candidate)) { return candidate } } @@ -398,11 +399,9 @@ async function resolveConfigPath(baseDir: string, global = false) { } async function addMcpToConfig(name: string, mcpConfig: Config.Mcp, configPath: string) { - const file = Bun.file(configPath) - let text = "{}" - if (await file.exists()) { - text = await file.text() + if (await Filesystem.exists(configPath)) { + text = await Filesystem.readText(configPath) } // Use jsonc-parser to modify while preserving comments @@ -411,7 +410,7 @@ async function addMcpToConfig(name: string, mcpConfig: Config.Mcp, configPath: s }) const result = applyEdits(text, edits) - await Bun.write(configPath, result) + await Filesystem.write(configPath, result) return configPath } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 261731b8b..dfdcb0343 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -255,19 +255,20 @@ export namespace Config { const pkg = path.join(dir, "package.json") const targetVersion = Installation.isLocal() ? "*" : Installation.VERSION - const json = await Bun.file(pkg) - .json() - .catch(() => ({})) + const json = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => ({ + dependencies: {}, + })) json.dependencies = { ...json.dependencies, "@opencode-ai/plugin": targetVersion, } - await Bun.write(pkg, JSON.stringify(json, null, 2)) + await Filesystem.writeJson(pkg, json) await new Promise((resolve) => setTimeout(resolve, 3000)) const gitignore = path.join(dir, ".gitignore") - const hasGitIgnore = await Bun.file(gitignore).exists() - if (!hasGitIgnore) await Bun.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n")) + const hasGitIgnore = await Filesystem.exists(gitignore) + if (!hasGitIgnore) + await Filesystem.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n")) // Install any additional dependencies defined in the package.json // This allows local plugins and custom tools to use external packages @@ -303,11 +304,10 @@ export namespace Config { if (!existsSync(nodeModules)) return true const pkg = path.join(dir, "package.json") - const pkgFile = Bun.file(pkg) - const pkgExists = await pkgFile.exists() + const pkgExists = await Filesystem.exists(pkg) if (!pkgExists) return true - const parsed = await pkgFile.json().catch(() => null) + const parsed = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => null) const dependencies = parsed?.dependencies ?? {} const depVersion = dependencies["@opencode-ai/plugin"] if (!depVersion) return true @@ -1220,7 +1220,7 @@ export namespace Config { if (provider && model) result.model = `${provider}/${model}` result["$schema"] = "https://opencode.ai/config.json" result = mergeDeep(result, rest) - await Bun.write(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2)) + await Filesystem.writeJson(path.join(Global.Path.config, "config.json"), result) await fs.unlink(legacy) }) .catch(() => {}) @@ -1231,12 +1231,10 @@ export namespace Config { async function loadFile(filepath: string): Promise { log.info("loading", { path: filepath }) - let text = await Bun.file(filepath) - .text() - .catch((err) => { - if (err.code === "ENOENT") return - throw new JsonError({ path: filepath }, { cause: err }) - }) + let text = await Filesystem.readText(filepath).catch((err: any) => { + if (err.code === "ENOENT") return + throw new JsonError({ path: filepath }, { cause: err }) + }) if (!text) return {} return load(text, filepath) } @@ -1263,21 +1261,19 @@ export namespace Config { } const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath) const fileContent = ( - await Bun.file(resolvedPath) - .text() - .catch((error) => { - const errMsg = `bad file reference: "${match}"` - if (error.code === "ENOENT") { - throw new InvalidError( - { - path: configFilepath, - message: errMsg + ` ${resolvedPath} does not exist`, - }, - { cause: error }, - ) - } - throw new InvalidError({ path: configFilepath, message: errMsg }, { cause: error }) - }) + await Filesystem.readText(resolvedPath).catch((error: any) => { + const errMsg = `bad file reference: "${match}"` + if (error.code === "ENOENT") { + throw new InvalidError( + { + path: configFilepath, + message: errMsg + ` ${resolvedPath} does not exist`, + }, + { cause: error }, + ) + } + throw new InvalidError({ path: configFilepath, message: errMsg }, { cause: error }) + }) ).trim() // escape newlines/quotes, strip outer quotes text = text.replace(match, () => JSON.stringify(fileContent).slice(1, -1)) @@ -1314,7 +1310,7 @@ export namespace Config { parsed.data.$schema = "https://opencode.ai/config.json" // Write the $schema to the original text to preserve variables like {env:VAR} const updated = original.replace(/^\s*\{/, '{\n "$schema": "https://opencode.ai/config.json",') - await Bun.write(configFilepath, updated).catch(() => {}) + await Filesystem.write(configFilepath, updated).catch(() => {}) } const data = parsed.data if (data.plugin) { @@ -1370,7 +1366,7 @@ export namespace Config { export async function update(config: Info) { const filepath = path.join(Instance.directory, "config.json") const existing = await loadFile(filepath) - await Bun.write(filepath, JSON.stringify(mergeDeep(existing, config), null, 2)) + await Filesystem.writeJson(filepath, mergeDeep(existing, config)) await Instance.dispose() } @@ -1441,24 +1437,22 @@ export namespace Config { export async function updateGlobal(config: Info) { const filepath = globalConfigFile() - const before = await Bun.file(filepath) - .text() - .catch((err) => { - if (err.code === "ENOENT") return "{}" - throw new JsonError({ path: filepath }, { cause: err }) - }) + const before = await Filesystem.readText(filepath).catch((err: any) => { + if (err.code === "ENOENT") return "{}" + throw new JsonError({ path: filepath }, { cause: err }) + }) const next = await (async () => { if (!filepath.endsWith(".jsonc")) { const existing = parseConfig(before, filepath) const merged = mergeDeep(existing, config) - await Bun.write(filepath, JSON.stringify(merged, null, 2)) + await Filesystem.writeJson(filepath, merged) return merged } const updated = patchJsonc(before, config) const merged = parseConfig(updated, filepath) - await Bun.write(filepath, updated) + await Filesystem.write(filepath, updated) return merged })() diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 58f9af7cd..ca1eadae8 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -6,6 +6,7 @@ import z from "zod" import { NamedError } from "@opencode-ai/util/error" import { lazy } from "../util/lazy" import { $ } from "bun" +import { Filesystem } from "../util/filesystem" import { ZipReader, BlobReader, BlobWriter } from "@zip.js/zip.js" import { Log } from "@/util/log" @@ -131,8 +132,7 @@ export namespace Ripgrep { } const filepath = path.join(Global.Path.bin, "rg" + (process.platform === "win32" ? ".exe" : "")) - const file = Bun.file(filepath) - if (!(await file.exists())) { + if (!(await Filesystem.exists(filepath))) { const platformKey = `${process.arch}-${process.platform}` as keyof typeof PLATFORM const config = PLATFORM[platformKey] if (!config) throw new UnsupportedPlatformError({ platform: platformKey }) @@ -144,9 +144,9 @@ export namespace Ripgrep { const response = await fetch(url) if (!response.ok) throw new DownloadFailedError({ url, status: response.status }) - const buffer = await response.arrayBuffer() + const arrayBuffer = await response.arrayBuffer() const archivePath = path.join(Global.Path.bin, filename) - await Bun.write(archivePath, buffer) + await Filesystem.write(archivePath, Buffer.from(arrayBuffer)) if (config.extension === "tar.gz") { const args = ["tar", "-xzf", archivePath, "--strip-components=1"] @@ -166,7 +166,7 @@ export namespace Ripgrep { }) } if (config.extension === "zip") { - const zipFileReader = new ZipReader(new BlobReader(new Blob([await Bun.file(archivePath).arrayBuffer()]))) + const zipFileReader = new ZipReader(new BlobReader(new Blob([arrayBuffer]))) const entries = await zipFileReader.getEntries() let rgEntry: any for (const entry of entries) { @@ -190,7 +190,7 @@ export namespace Ripgrep { stderr: "Failed to extract rg.exe from zip archive", }) } - await Bun.write(filepath, await rgBlob.arrayBuffer()) + await Filesystem.write(filepath, Buffer.from(await rgBlob.arrayBuffer())) await zipFileReader.close() } await fs.unlink(archivePath)