refactor(core): consolidate filesystem services (#30447)

This commit is contained in:
Dax
2026-06-02 16:09:26 -04:00
committed by GitHub
parent b93963e462
commit 604a5f781f
153 changed files with 2553 additions and 4312 deletions

View File

@@ -2,7 +2,7 @@ import { expect } from "bun:test"
import path from "path"
import { pathToFileURL } from "url"
import { Effect, Layer } from "effect"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { Global } from "@opencode-ai/core/global"
import { Config } from "@/config/config"
import { ConfigPlugin } from "@/config/plugin"
@@ -11,7 +11,7 @@ import { TuiConfig } from "../../src/cli/cmd/tui/config/tui"
import { TestInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
const it = testEffect(Layer.mergeAll(Config.defaultLayer, AppFileSystem.defaultLayer))
const it = testEffect(Layer.mergeAll(Config.defaultLayer, FSUtil.defaultLayer))
const winIt = process.platform === "win32" ? it.instance : it.instance.skip
const globalConfigFiles = ["opencode.json", "opencode.jsonc", "tui.json", "tui.jsonc"].map((file) =>
@@ -19,7 +19,7 @@ const globalConfigFiles = ["opencode.json", "opencode.jsonc", "tui.json", "tui.j
)
const cleanState = Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
delete process.env.OPENCODE_CONFIG
delete process.env.OPENCODE_TUI_CONFIG
yield* Effect.forEach(globalConfigFiles, (file) => fs.remove(file, { force: true }).pipe(Effect.ignore), {
@@ -75,7 +75,7 @@ const getTuiConfig = (directory: string) =>
it.instance("keeps server and tui plugin merge semantics aligned", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
const local = path.join(test.directory, ".opencode")
yield* fs.makeDirectory(local, { recursive: true })
@@ -114,7 +114,7 @@ it.instance("keeps server and tui plugin merge semantics aligned", () =>
it.instance("loads tui config with the same precedence order as server config paths", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(Global.Path.config, "tui.json"), { theme: "global" })
yield* fs.writeJson(path.join(test.directory, "tui.json"), { theme: "project" })
@@ -133,7 +133,7 @@ it.instance("loads tui config with the same precedence order as server config pa
it.instance("resolves attention config defaults and overrides", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
expect((yield* getTuiConfig(test.directory)).attention).toEqual({
@@ -181,7 +181,7 @@ it.instance("resolves attention config defaults and overrides", () =>
it.instance("migrates tui-specific keys from opencode.json when tui.json does not exist", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
const source = path.join(test.directory, "opencode.json")
yield* fs.writeJson(source, {
@@ -211,7 +211,7 @@ it.instance("migrates tui-specific keys from opencode.json when tui.json does no
it.instance("migrates project legacy tui keys even when global tui.json already exists", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(Global.Path.config, "tui.json"), { theme: "global" })
yield* fs.writeJson(path.join(test.directory, "opencode.json"), {
@@ -234,7 +234,7 @@ it.instance("migrates project legacy tui keys even when global tui.json already
it.instance("drops unknown legacy tui keys during migration", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(test.directory, "opencode.json"), {
theme: "migrated-theme",
@@ -255,7 +255,7 @@ it.instance("drops unknown legacy tui keys during migration", () =>
it.instance("skips migration when opencode.jsonc is syntactically invalid", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeFileString(
path.join(test.directory, "opencode.jsonc"),
@@ -281,7 +281,7 @@ it.instance("skips migration when opencode.jsonc is syntactically invalid", () =
it.instance("skips migration when tui.json already exists", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(test.directory, "opencode.json"), { theme: "legacy" })
yield* fs.writeJson(path.join(test.directory, "tui.json"), { diff_style: "stacked" })
@@ -300,7 +300,7 @@ it.instance("skips migration when tui.json already exists", () =>
it.instance("continues loading tui config when legacy source cannot be stripped", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
const source = path.join(test.directory, "opencode.json")
yield* fs.writeJson(source, { theme: "readonly-theme" })
@@ -325,7 +325,7 @@ it.instance("continues loading tui config when legacy source cannot be stripped"
it.instance("migration backup preserves JSONC comments", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeFileString(
path.join(test.directory, "opencode.jsonc"),
@@ -352,7 +352,7 @@ it.instance("migration backup preserves JSONC comments", () =>
it.instance("migrates legacy tui keys across multiple opencode.json levels", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
const nested = path.join(test.directory, "apps", "client")
yield* fs.makeDirectory(nested, { recursive: true })
@@ -370,7 +370,7 @@ it.instance("migrates legacy tui keys across multiple opencode.json levels", ()
it.instance("flattens nested tui key inside tui.json", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(test.directory, "tui.json"), {
theme: "outer",
@@ -388,7 +388,7 @@ it.instance("flattens nested tui key inside tui.json", () =>
it.instance("top-level keys in tui.json take precedence over nested tui key", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(test.directory, "tui.json"), {
diff_style: "auto",
@@ -405,7 +405,7 @@ it.instance("top-level keys in tui.json take precedence over nested tui key", ()
it.instance("project config takes precedence over OPENCODE_TUI_CONFIG (matches OPENCODE_CONFIG)", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
const custom = path.join(test.directory, "custom-tui.json")
yield* fs.writeJson(path.join(test.directory, "tui.json"), { theme: "project", diff_style: "auto" })
@@ -427,7 +427,7 @@ it.instance("project config takes precedence over OPENCODE_TUI_CONFIG (matches O
it.instance("merges keybind overrides across precedence layers", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(Global.Path.config, "tui.json"), { keybinds: { app_exit: "ctrl+q" } })
yield* fs.writeJson(path.join(test.directory, "tui.json"), { keybinds: { theme_list: "ctrl+k" } })
@@ -442,7 +442,7 @@ it.instance("merges keybind overrides across precedence layers", () =>
it.instance("ignores unknown keybind names without dropping valid overrides from the same file", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(Global.Path.config, "tui.json"), {
keybinds: {
@@ -461,7 +461,7 @@ it.instance("ignores unknown keybind names without dropping valid overrides from
it.instance("resolves keybind lookup from canonical keybinds", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(test.directory, "tui.json"), {
keybinds: {
@@ -506,7 +506,7 @@ it.instance("resolves keybind lookup from canonical keybinds", () =>
it.instance("keybinds accept OpenTUI binding specs", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(test.directory, "tui.json"), {
keybinds: {
@@ -546,7 +546,7 @@ winIt("defaults Ctrl+Z to input undo on Windows", () =>
winIt("keeps explicit input undo overrides on Windows", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(test.directory, "tui.json"), { keybinds: { input_undo: "ctrl+y" } })
@@ -560,7 +560,7 @@ winIt("keeps explicit input undo overrides on Windows", () =>
winIt("ignores terminal suspend bindings on Windows", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(test.directory, "tui.json"), { keybinds: { terminal_suspend: "alt+z" } })
@@ -590,7 +590,7 @@ it.instance("ignores explicit keybind terminal suspend binding on Windows", () =
withPlatform(
"win32",
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(test.directory, "tui.json"), {
keybinds: {
@@ -610,7 +610,7 @@ it.instance("keeps explicit configured keybind input undo on Windows", () =>
withPlatform(
"win32",
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(test.directory, "tui.json"), {
keybinds: {
@@ -628,7 +628,7 @@ it.instance("keeps explicit configured keybind input undo on Windows", () =>
it.instance("OPENCODE_TUI_CONFIG provides settings when no project config exists", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
const custom = path.join(test.directory, "custom-tui.json")
yield* fs.writeJson(custom, { theme: "from-env", diff_style: "stacked" })
@@ -649,7 +649,7 @@ it.instance("OPENCODE_TUI_CONFIG provides settings when no project config exists
it.instance("does not derive tui path from OPENCODE_CONFIG", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
const customDir = path.join(test.directory, "custom")
yield* fs.makeDirectory(customDir, { recursive: true })
@@ -674,7 +674,7 @@ it.instance("applies env and file substitutions in tui.json", () =>
"TUI_THEME_TEST",
"env-theme",
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeFileString(path.join(test.directory, "keybind.txt"), "ctrl+q")
yield* fs.writeJson(path.join(test.directory, "tui.json"), {
@@ -693,7 +693,7 @@ it.instance("applies env and file substitutions in tui.json", () =>
it.instance("applies file substitutions when first identical token is in a commented line", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeFileString(path.join(test.directory, "theme.txt"), "resolved-theme")
yield* fs.writeFileString(
@@ -713,7 +713,7 @@ it.instance("applies file substitutions when first identical token is in a comme
it.instance("loads .opencode/tui.json", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeWithDirs(
path.join(test.directory, ".opencode", "tui.json"),
@@ -729,7 +729,7 @@ it.instance("loads .opencode/tui.json", () =>
it.instance("supports tuple plugin specs with options in tui.json", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(test.directory, "tui.json"), {
plugin: [["acme-plugin@1.2.3", { enabled: true, label: "demo" }]],
@@ -751,7 +751,7 @@ it.instance("supports tuple plugin specs with options in tui.json", () =>
it.instance("deduplicates tuple plugin specs by name with higher precedence winning", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(Global.Path.config, "tui.json"), {
plugin: [["acme-plugin@1.0.0", { source: "global" }]],
@@ -787,7 +787,7 @@ it.instance("deduplicates tuple plugin specs by name with higher precedence winn
it.instance("tracks global and local plugin metadata in merged tui config", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(Global.Path.config, "tui.json"), { plugin: ["global-plugin@1.0.0"] })
yield* fs.writeJson(path.join(test.directory, "tui.json"), { plugin: ["local-plugin@2.0.0"] })
@@ -813,7 +813,7 @@ it.instance("tracks global and local plugin metadata in merged tui config", () =
it.instance("merges plugin_enabled flags across config layers", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeJson(path.join(Global.Path.config, "tui.json"), {
plugin_enabled: {
@@ -841,7 +841,7 @@ it.instance("merges plugin_enabled flags across config layers", () =>
it.instance("silently skips malformed tui.json - load failures degrade to {}", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.writeFileString(path.join(test.directory, "tui.json"), '{ "theme": "broken",')
yield* fs.writeWithDirs(path.join(test.directory, ".opencode", "tui.json"), JSON.stringify({ theme: "fallback" }))
@@ -855,7 +855,7 @@ it.instance("silently skips malformed tui.json - load failures degrade to {}", (
it.instance("silently skips non-ENOENT read failures (e.g. tui.json is a directory) - fallback layer still loads", () =>
withCleanState(
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const test = yield* TestInstance
yield* fs.makeDirectory(path.join(test.directory, "tui.json"), { recursive: true })
yield* fs.writeWithDirs(path.join(test.directory, ".opencode", "tui.json"), JSON.stringify({ theme: "fallback" }))