fix(core): load config across git boundaries (#36577)
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
This commit is contained in:
parent
7e9b9cb0fd
commit
f112a73c06
6 changed files with 17 additions and 40 deletions
|
|
@ -200,7 +200,6 @@ const layer = Layer.effect(
|
|||
.up({
|
||||
targets: [".opencode", ".claude", ".agents", ...names.toReversed()],
|
||||
start: location.directory,
|
||||
stop: location.project.directory,
|
||||
})
|
||||
.pipe(Effect.orDie)
|
||||
|
||||
|
|
|
|||
|
|
@ -876,7 +876,7 @@ describe("Config", () => {
|
|||
),
|
||||
)
|
||||
|
||||
it.live("loads global, ancestor, and .opencode configuration up to the project boundary", () =>
|
||||
it.live("loads global and ancestor configuration across the project boundary", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||
|
|
@ -936,6 +936,7 @@ describe("Config", () => {
|
|||
])
|
||||
expect(documents.map((document) => document.info.$schema)).toEqual([
|
||||
"global",
|
||||
"outside",
|
||||
"root",
|
||||
"parent",
|
||||
"directory",
|
||||
|
|
@ -951,6 +952,8 @@ describe("Config", () => {
|
|||
AbsolutePath.make(path.join(root, ".agents")),
|
||||
"global",
|
||||
AbsolutePath.make(global),
|
||||
"outside",
|
||||
AbsolutePath.make(path.join(tmp.path, "opencode.json")),
|
||||
"root",
|
||||
AbsolutePath.make(path.join(root, "opencode.json")),
|
||||
"parent",
|
||||
|
|
|
|||
|
|
@ -404,7 +404,7 @@ const layer = Layer.effect(
|
|||
}
|
||||
|
||||
if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) {
|
||||
for (const file of yield* ConfigPaths.files("opencode", ctx.directory).pipe(Effect.orDie)) {
|
||||
for (const file of yield* ConfigPaths.files("opencode", ctx.directory, ctx.worktree).pipe(Effect.orDie)) {
|
||||
yield* merge(file, yield* loadFile(file, authEnv), "local")
|
||||
}
|
||||
}
|
||||
|
|
@ -413,7 +413,7 @@ const layer = Layer.effect(
|
|||
result.mode = result.mode || {}
|
||||
result.plugin = result.plugin || []
|
||||
|
||||
const directories = yield* ConfigPaths.directories(ctx.directory)
|
||||
const directories = yield* ConfigPaths.directories(ctx.directory, ctx.worktree)
|
||||
|
||||
if (Flag.OPENCODE_CONFIG_DIR) {
|
||||
yield* Effect.logDebug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR })
|
||||
|
|
|
|||
|
|
@ -7,23 +7,29 @@ import { unique } from "remeda"
|
|||
import * as Effect from "effect/Effect"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
|
||||
export const files = Effect.fn("ConfigPaths.projectFiles")(function* (name: string, directory: string) {
|
||||
export const files = Effect.fn("ConfigPaths.projectFiles")(function* (
|
||||
name: string,
|
||||
directory: string,
|
||||
worktree?: string,
|
||||
) {
|
||||
const afs = yield* FSUtil.Service
|
||||
return (yield* afs.up({
|
||||
targets: [`${name}.jsonc`, `${name}.json`],
|
||||
start: directory,
|
||||
stop: worktree,
|
||||
})).toReversed()
|
||||
})
|
||||
|
||||
export const directories = Effect.fn("ConfigPaths.directories")(function* (directory: string) {
|
||||
export const directories = Effect.fn("ConfigPaths.directories")(function* (directory: string, worktree?: string) {
|
||||
const afs = yield* FSUtil.Service
|
||||
return unique([
|
||||
Global.Path.config,
|
||||
...(!Flag.OPENCODE_DISABLE_PROJECT_CONFIG
|
||||
? (yield* afs.up({
|
||||
? yield* afs.up({
|
||||
targets: [".opencode"],
|
||||
start: directory,
|
||||
})).toReversed()
|
||||
stop: worktree,
|
||||
})
|
||||
: []),
|
||||
...(yield* afs.up({
|
||||
targets: [".opencode"],
|
||||
|
|
|
|||
|
|
@ -999,37 +999,6 @@ it.instance("resolves scoped npm plugins in config", () =>
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("loads shared config above a child git repository", () =>
|
||||
Effect.gen(function* () {
|
||||
const root = yield* tmpdirScoped()
|
||||
const global = yield* tmpdirScoped()
|
||||
const workspace = path.join(root, "workspace")
|
||||
const project = path.join(workspace, "project")
|
||||
|
||||
yield* writeConfigEffect(path.join(workspace, ".opencode"), {
|
||||
model: "shared/model",
|
||||
username: "shared-user",
|
||||
})
|
||||
yield* writeConfigEffect(path.join(project, ".opencode"), { username: "project-user" })
|
||||
yield* Effect.promise(async () => {
|
||||
const child = Bun.spawn(["git", "init"], { cwd: project, stdout: "ignore", stderr: "ignore" })
|
||||
await child.exited
|
||||
})
|
||||
|
||||
yield* withGlobalConfigDir(
|
||||
global,
|
||||
withInstanceDir(
|
||||
project,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.use.get()
|
||||
expect(config.model).toBe("shared/model")
|
||||
expect(config.username).toBe("project-user")
|
||||
}),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("merges plugin arrays from global and local configs", () =>
|
||||
withConfigTree(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ For project-specific TUI settings, add `tui.json` alongside it.
|
|||
Place project specific config in the root of your project.
|
||||
:::
|
||||
|
||||
When OpenCode starts up, it looks for config files in the current directory and every ancestor directory. Ancestor configs are loaded first, so configs closer to the current directory can override them. This traversal continues across Git repository boundaries.
|
||||
When OpenCode starts up, it first looks for a config file in the current directory, then traverses up to the nearest Git directory.
|
||||
|
||||
This is also safe to be checked into Git and uses the same schema as the global one.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue