core: migrate config loading to Effect framework (#23032)
This commit is contained in:
@@ -135,7 +135,9 @@ export function tui(input: {
|
||||
await TuiPluginRuntime.dispose()
|
||||
}
|
||||
|
||||
console.log("starting renderer")
|
||||
const renderer = await createCliRenderer(rendererConfig(input.config))
|
||||
console.log("renderer started")
|
||||
|
||||
await render(() => {
|
||||
return (
|
||||
|
||||
@@ -132,8 +132,10 @@ async function backupAndStripLegacy(file: string, source: string) {
|
||||
}
|
||||
|
||||
async function opencodeFiles(input: { directories: string[]; cwd: string }) {
|
||||
const project = Flag.OPENCODE_DISABLE_PROJECT_CONFIG ? [] : await ConfigPaths.projectFiles("opencode", input.cwd)
|
||||
const files = [...project, ...ConfigPaths.fileInDirectory(Global.Path.config, "opencode")]
|
||||
const files = [
|
||||
...ConfigPaths.fileInDirectory(Global.Path.config, "opencode"),
|
||||
...(await Filesystem.findUp(["opencode.json", "opencode.jsonc"], input.cwd, undefined, { rootFirst: true })),
|
||||
]
|
||||
for (const dir of unique(input.directories)) {
|
||||
files.push(...ConfigPaths.fileInDirectory(dir, "opencode"))
|
||||
}
|
||||
|
||||
@@ -89,15 +89,13 @@ async function mergeFile(acc: Acc, file: string, ctx: { directory: string }) {
|
||||
acc.result.plugin_origins = plugins
|
||||
}
|
||||
|
||||
async function loadState(ctx: { directory: string }) {
|
||||
const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory: string }) {
|
||||
// Every config dir we may read from: global config dir, any `.opencode`
|
||||
// folders between cwd and home, and OPENCODE_CONFIG_DIR.
|
||||
const directories = await ConfigPaths.directories(ctx.directory)
|
||||
// One-time migration: extract tui keys (theme/keybinds/tui) from existing
|
||||
// opencode.json files into sibling tui.json files.
|
||||
await migrateTuiConfig({ directories, cwd: ctx.directory })
|
||||
const directories = yield* ConfigPaths.directories(ctx.directory)
|
||||
yield* Effect.promise(() => migrateTuiConfig({ directories, cwd: ctx.directory }))
|
||||
|
||||
const projectFiles = Flag.OPENCODE_DISABLE_PROJECT_CONFIG ? [] : await ConfigPaths.projectFiles("tui", ctx.directory)
|
||||
const projectFiles = Flag.OPENCODE_DISABLE_PROJECT_CONFIG ? [] : yield* ConfigPaths.files("tui", ctx.directory)
|
||||
|
||||
const acc: Acc = {
|
||||
result: {},
|
||||
@@ -105,18 +103,19 @@ async function loadState(ctx: { directory: string }) {
|
||||
|
||||
// 1. Global tui config (lowest precedence).
|
||||
for (const file of ConfigPaths.fileInDirectory(Global.Path.config, "tui")) {
|
||||
await mergeFile(acc, file, ctx)
|
||||
yield* Effect.promise(() => mergeFile(acc, file, ctx)).pipe(Effect.orDie)
|
||||
}
|
||||
|
||||
// 2. Explicit OPENCODE_TUI_CONFIG override, if set.
|
||||
if (Flag.OPENCODE_TUI_CONFIG) {
|
||||
await mergeFile(acc, Flag.OPENCODE_TUI_CONFIG, ctx)
|
||||
log.debug("loaded custom tui config", { path: Flag.OPENCODE_TUI_CONFIG })
|
||||
const configFile = Flag.OPENCODE_TUI_CONFIG
|
||||
yield* Effect.promise(() => mergeFile(acc, configFile, ctx)).pipe(Effect.orDie)
|
||||
log.debug("loaded custom tui config", { path: configFile })
|
||||
}
|
||||
|
||||
// 3. Project tui files, applied root-first so the closest file wins.
|
||||
for (const file of projectFiles) {
|
||||
await mergeFile(acc, file, ctx)
|
||||
yield* Effect.promise(() => mergeFile(acc, file, ctx)).pipe(Effect.orDie)
|
||||
}
|
||||
|
||||
// 4. `.opencode` directories (and OPENCODE_CONFIG_DIR) discovered while
|
||||
@@ -127,7 +126,7 @@ async function loadState(ctx: { directory: string }) {
|
||||
for (const dir of dirs) {
|
||||
if (!dir.endsWith(".opencode") && dir !== Flag.OPENCODE_CONFIG_DIR) continue
|
||||
for (const file of ConfigPaths.fileInDirectory(dir, "tui")) {
|
||||
await mergeFile(acc, file, ctx)
|
||||
yield* Effect.promise(() => mergeFile(acc, file, ctx)).pipe(Effect.orDie)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,14 +145,14 @@ async function loadState(ctx: { directory: string }) {
|
||||
config: acc.result,
|
||||
dirs: acc.result.plugin?.length ? dirs : [],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const directory = yield* CurrentWorkingDirectory
|
||||
const npm = yield* Npm.Service
|
||||
const data = yield* Effect.promise(() => loadState({ directory }))
|
||||
const data = yield* loadState({ directory })
|
||||
const deps = yield* Effect.forEach(
|
||||
data.dirs,
|
||||
(dir) =>
|
||||
@@ -176,7 +175,7 @@ export const layer = Layer.effect(
|
||||
}).pipe(Effect.withSpan("TuiConfig.layer")),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Npm.defaultLayer))
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Npm.defaultLayer), Layer.provide(AppFileSystem.defaultLayer))
|
||||
|
||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||
|
||||
|
||||
@@ -413,7 +413,8 @@ export const layer = Layer.effect(
|
||||
}
|
||||
})
|
||||
|
||||
const loadInstanceState = Effect.fn("Config.loadInstanceState")(function* (ctx: InstanceContext) {
|
||||
const loadInstanceState = Effect.fn("Config.loadInstanceState")(
|
||||
function* (ctx: InstanceContext) {
|
||||
const auth = yield* authSvc.all().pipe(Effect.orDie)
|
||||
|
||||
let result: Info = {}
|
||||
@@ -484,9 +485,7 @@ export const layer = Layer.effect(
|
||||
}
|
||||
|
||||
if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) {
|
||||
for (const file of yield* Effect.promise(() =>
|
||||
ConfigPaths.projectFiles("opencode", ctx.directory, ctx.worktree),
|
||||
)) {
|
||||
for (const file of yield* ConfigPaths.files("opencode", ctx.directory, ctx.worktree).pipe(Effect.orDie)) {
|
||||
yield* merge(file, yield* loadFile(file), "local")
|
||||
}
|
||||
}
|
||||
@@ -495,7 +494,7 @@ export const layer = Layer.effect(
|
||||
result.mode = result.mode || {}
|
||||
result.plugin = result.plugin || []
|
||||
|
||||
const directories = yield* Effect.promise(() => ConfigPaths.directories(ctx.directory, ctx.worktree))
|
||||
const directories = yield* ConfigPaths.directories(ctx.directory, ctx.worktree)
|
||||
|
||||
if (Flag.OPENCODE_CONFIG_DIR) {
|
||||
log.debug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR })
|
||||
@@ -662,11 +661,13 @@ export const layer = Layer.effect(
|
||||
switchableOrgCount: 0,
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
Effect.provideService(AppFileSystem.Service, fs),
|
||||
)
|
||||
|
||||
const state = yield* InstanceState.make<State>(
|
||||
Effect.fn("Config.state")(function* (ctx) {
|
||||
return yield* loadInstanceState(ctx)
|
||||
return yield* loadInstanceState(ctx).pipe(Effect.orDie)
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -6,33 +6,41 @@ import { Flag } from "@/flag/flag"
|
||||
import { Global } from "@/global"
|
||||
import { unique } from "remeda"
|
||||
import { JsonError } from "./error"
|
||||
import * as Effect from "effect/Effect"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
|
||||
export async function projectFiles(name: string, directory: string, worktree?: string) {
|
||||
return Filesystem.findUp([`${name}.json`, `${name}.jsonc`], directory, worktree, { rootFirst: true })
|
||||
}
|
||||
export const files = Effect.fn("ConfigPaths.projectFiles")(function* (
|
||||
name: string,
|
||||
directory: string,
|
||||
worktree?: string,
|
||||
) {
|
||||
const afs = yield* AppFileSystem.Service
|
||||
return (yield* afs.up({
|
||||
targets: [`${name}.jsonc`, `${name}.json`],
|
||||
start: directory,
|
||||
stop: worktree,
|
||||
})).toReversed()
|
||||
})
|
||||
|
||||
export async function directories(directory: string, worktree?: string) {
|
||||
export const directories = Effect.fn("ConfigPaths.directories")(function* (directory: string, worktree?: string) {
|
||||
const afs = yield* AppFileSystem.Service
|
||||
return unique([
|
||||
Global.Path.config,
|
||||
...(!Flag.OPENCODE_DISABLE_PROJECT_CONFIG
|
||||
? await Array.fromAsync(
|
||||
Filesystem.up({
|
||||
? yield* afs.up({
|
||||
targets: [".opencode"],
|
||||
start: directory,
|
||||
stop: worktree,
|
||||
}),
|
||||
)
|
||||
})
|
||||
: []),
|
||||
...(await Array.fromAsync(
|
||||
Filesystem.up({
|
||||
...(yield* afs.up({
|
||||
targets: [".opencode"],
|
||||
start: Global.Path.home,
|
||||
stop: Global.Path.home,
|
||||
}),
|
||||
)),
|
||||
})),
|
||||
...(Flag.OPENCODE_CONFIG_DIR ? [Flag.OPENCODE_CONFIG_DIR] : []),
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
export function fileInDirectory(dir: string, name: string) {
|
||||
return [path.join(dir, `${name}.json`), path.join(dir, `${name}.jsonc`)]
|
||||
|
||||
Reference in New Issue
Block a user