Merge pull request #3875 from jollyxenon/fix/3846-opencode-config-dir-additive
fix(config): align OPENCODE_CONFIG_DIR with additive OpenCode semantics
This commit is contained in:
@@ -1,34 +1,43 @@
|
||||
import { describe, expect, it, mock, beforeEach, afterEach } from "bun:test"
|
||||
import { join } from "node:path"
|
||||
import { resolve } from "node:path"
|
||||
|
||||
describe("opencode-command-dirs", () => {
|
||||
let originalEnv: string | undefined
|
||||
let originalOpencodeConfigDir: string | undefined
|
||||
let originalXdgConfigHome: string | undefined
|
||||
|
||||
beforeEach(() => {
|
||||
originalEnv = process.env.OPENCODE_CONFIG_DIR
|
||||
originalOpencodeConfigDir = process.env.OPENCODE_CONFIG_DIR
|
||||
originalXdgConfigHome = process.env.XDG_CONFIG_HOME
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (originalEnv !== undefined) {
|
||||
process.env.OPENCODE_CONFIG_DIR = originalEnv
|
||||
if (originalOpencodeConfigDir !== undefined) {
|
||||
process.env.OPENCODE_CONFIG_DIR = originalOpencodeConfigDir
|
||||
} else {
|
||||
delete process.env.OPENCODE_CONFIG_DIR
|
||||
}
|
||||
|
||||
if (originalXdgConfigHome !== undefined) {
|
||||
process.env.XDG_CONFIG_HOME = originalXdgConfigHome
|
||||
} else {
|
||||
delete process.env.XDG_CONFIG_HOME
|
||||
}
|
||||
})
|
||||
|
||||
describe("getOpenCodeSkillDirs", () => {
|
||||
describe("#given config dir inside profiles/", () => {
|
||||
describe("#when getOpenCodeSkillDirs is called", () => {
|
||||
it("#then returns both profile and parent skill dirs", async () => {
|
||||
process.env.XDG_CONFIG_HOME = "/home/user/.config"
|
||||
process.env.OPENCODE_CONFIG_DIR = "/home/user/.config/opencode/profiles/opus"
|
||||
|
||||
const { getOpenCodeSkillDirs } = await import("./opencode-command-dirs")
|
||||
const dirs = getOpenCodeSkillDirs({ binary: "opencode" })
|
||||
|
||||
expect(dirs).toContain("/home/user/.config/opencode/profiles/opus/skills")
|
||||
expect(dirs).toContain("/home/user/.config/opencode/profiles/opus/skill")
|
||||
expect(dirs).toContain("/home/user/.config/opencode/skill")
|
||||
expect(dirs).toContain("/home/user/.config/opencode/skills")
|
||||
expect(dirs).toContain(resolve("/home/user/.config/opencode/profiles/opus/skills"))
|
||||
expect(dirs).toContain(resolve("/home/user/.config/opencode/profiles/opus/skill"))
|
||||
expect(dirs).toContain(resolve("/home/user/.config/opencode/skill"))
|
||||
expect(dirs).toContain(resolve("/home/user/.config/opencode/skills"))
|
||||
expect(dirs).toHaveLength(4)
|
||||
})
|
||||
})
|
||||
@@ -37,13 +46,14 @@ describe("opencode-command-dirs", () => {
|
||||
describe("#given config dir NOT inside profiles/", () => {
|
||||
describe("#when getOpenCodeSkillDirs is called", () => {
|
||||
it("#then returns only the config dir skills", async () => {
|
||||
process.env.XDG_CONFIG_HOME = "/home/user/.config"
|
||||
process.env.OPENCODE_CONFIG_DIR = "/home/user/.config/opencode"
|
||||
|
||||
const { getOpenCodeSkillDirs } = await import("./opencode-command-dirs")
|
||||
const dirs = getOpenCodeSkillDirs({ binary: "opencode" })
|
||||
|
||||
expect(dirs).toContain("/home/user/.config/opencode/skills")
|
||||
expect(dirs).toContain("/home/user/.config/opencode/skill")
|
||||
expect(dirs).toContain(resolve("/home/user/.config/opencode/skills"))
|
||||
expect(dirs).toContain(resolve("/home/user/.config/opencode/skill"))
|
||||
expect(dirs).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
@@ -54,15 +64,16 @@ describe("opencode-command-dirs", () => {
|
||||
describe("#given config dir inside profiles/", () => {
|
||||
describe("#when getOpenCodeCommandDirs is called", () => {
|
||||
it("#then returns both profile and parent command dirs", async () => {
|
||||
process.env.XDG_CONFIG_HOME = "/home/user/.config"
|
||||
process.env.OPENCODE_CONFIG_DIR = "/home/user/.config/opencode/profiles/opus"
|
||||
|
||||
const { getOpenCodeCommandDirs } = await import("./opencode-command-dirs")
|
||||
const dirs = getOpenCodeCommandDirs({ binary: "opencode" })
|
||||
|
||||
expect(dirs).toContain("/home/user/.config/opencode/profiles/opus/commands")
|
||||
expect(dirs).toContain("/home/user/.config/opencode/profiles/opus/command")
|
||||
expect(dirs).toContain("/home/user/.config/opencode/commands")
|
||||
expect(dirs).toContain("/home/user/.config/opencode/command")
|
||||
expect(dirs).toContain(resolve("/home/user/.config/opencode/profiles/opus/commands"))
|
||||
expect(dirs).toContain(resolve("/home/user/.config/opencode/profiles/opus/command"))
|
||||
expect(dirs).toContain(resolve("/home/user/.config/opencode/commands"))
|
||||
expect(dirs).toContain(resolve("/home/user/.config/opencode/command"))
|
||||
expect(dirs).toHaveLength(4)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { basename, dirname, join } from "node:path"
|
||||
import { getOpenCodeConfigDir } from "./opencode-config-dir"
|
||||
import { getOpenCodeConfigDirs } from "./opencode-config-dir"
|
||||
import type { OpenCodeConfigDirOptions } from "./opencode-config-dir-types"
|
||||
|
||||
function getParentOpencodeConfigDir(configDir: string): string | null {
|
||||
@@ -12,25 +12,33 @@ function getParentOpencodeConfigDir(configDir: string): string | null {
|
||||
}
|
||||
|
||||
export function getOpenCodeCommandDirs(options: OpenCodeConfigDirOptions): string[] {
|
||||
const configDir = getOpenCodeConfigDir(options)
|
||||
const parentConfigDir = getParentOpencodeConfigDir(configDir)
|
||||
const configDirs = getOpenCodeConfigDirs(options)
|
||||
return Array.from(
|
||||
new Set([
|
||||
join(configDir, "commands"),
|
||||
join(configDir, "command"),
|
||||
...(parentConfigDir ? [join(parentConfigDir, "commands"), join(parentConfigDir, "command")] : []),
|
||||
...configDirs.flatMap((configDir) => {
|
||||
const parentConfigDir = getParentOpencodeConfigDir(configDir)
|
||||
return [
|
||||
join(configDir, "commands"),
|
||||
join(configDir, "command"),
|
||||
...(parentConfigDir ? [join(parentConfigDir, "commands"), join(parentConfigDir, "command")] : []),
|
||||
]
|
||||
}),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
export function getOpenCodeSkillDirs(options: OpenCodeConfigDirOptions): string[] {
|
||||
const configDir = getOpenCodeConfigDir(options)
|
||||
const parentConfigDir = getParentOpencodeConfigDir(configDir)
|
||||
const configDirs = getOpenCodeConfigDirs(options)
|
||||
return Array.from(
|
||||
new Set([
|
||||
join(configDir, "skills"),
|
||||
join(configDir, "skill"),
|
||||
...(parentConfigDir ? [join(parentConfigDir, "skills"), join(parentConfigDir, "skill")] : []),
|
||||
...configDirs.flatMap((configDir) => {
|
||||
const parentConfigDir = getParentOpencodeConfigDir(configDir)
|
||||
return [
|
||||
join(configDir, "skills"),
|
||||
join(configDir, "skill"),
|
||||
...(parentConfigDir ? [join(parentConfigDir, "skills"), join(parentConfigDir, "skill")] : []),
|
||||
]
|
||||
}),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { homedir } from "node:os"
|
||||
import { join, resolve, win32 } from "node:path"
|
||||
import {
|
||||
getOpenCodeConfigDir,
|
||||
getOpenCodeConfigDirs,
|
||||
getOpenCodeConfigPaths,
|
||||
isDevBuild,
|
||||
detectExistingConfigDir,
|
||||
@@ -45,7 +46,7 @@ describe("opencode-config-dir", () => {
|
||||
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200" })
|
||||
|
||||
// then returns the custom path
|
||||
expect(result).toBe("/custom/opencode/path")
|
||||
expect(result).toBe(resolve("/custom/opencode/path"))
|
||||
})
|
||||
|
||||
test("falls back to default when env var is not set", () => {
|
||||
@@ -109,7 +110,23 @@ describe("opencode-config-dir", () => {
|
||||
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200" })
|
||||
|
||||
// then OPENCODE_CONFIG_DIR takes priority
|
||||
expect(result).toBe("/custom/opencode/path")
|
||||
expect(result).toBe(resolve("/custom/opencode/path"))
|
||||
})
|
||||
|
||||
test("returns both custom and default config directories for additive discovery", () => {
|
||||
// given both OPENCODE_CONFIG_DIR and XDG_CONFIG_HOME are set
|
||||
process.env.OPENCODE_CONFIG_DIR = "/custom/opencode/path"
|
||||
process.env.XDG_CONFIG_HOME = "/xdg/config"
|
||||
Object.defineProperty(process, "platform", { value: "linux" })
|
||||
|
||||
// when getOpenCodeConfigDirs is called
|
||||
const result = getOpenCodeConfigDirs({ binary: "opencode", version: "1.0.200" })
|
||||
|
||||
// then the custom path stays first, but the default global path remains visible
|
||||
expect(result).toEqual([
|
||||
resolve("/custom/opencode/path"),
|
||||
resolve("/xdg/config/opencode"),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -163,7 +180,7 @@ describe("opencode-config-dir", () => {
|
||||
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200" })
|
||||
|
||||
// then returns $XDG_CONFIG_HOME/opencode
|
||||
expect(result).toBe("/custom/config/opencode")
|
||||
expect(result).toBe(resolve("/custom/config/opencode"))
|
||||
})
|
||||
|
||||
test("returns ~/.config/opencode on macOS", () => {
|
||||
|
||||
@@ -55,16 +55,39 @@ function resolveConfigPath(pathValue: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
function getCliConfigDir(): string {
|
||||
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim()
|
||||
if (envConfigDir) {
|
||||
return resolveConfigPath(envConfigDir)
|
||||
}
|
||||
|
||||
function getCliDefaultConfigDir(): string {
|
||||
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), ".config")
|
||||
return resolveConfigPath(join(xdgConfig, "opencode"))
|
||||
}
|
||||
|
||||
function getCliCustomConfigDir(): string | null {
|
||||
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim()
|
||||
if (!envConfigDir) {
|
||||
return null
|
||||
}
|
||||
|
||||
return resolveConfigPath(envConfigDir)
|
||||
}
|
||||
|
||||
function getCliConfigDir(): string {
|
||||
return getCliCustomConfigDir() ?? getCliDefaultConfigDir()
|
||||
}
|
||||
|
||||
export function getOpenCodeConfigDirs(options: OpenCodeConfigDirOptions): string[] {
|
||||
if (options.binary !== "opencode") {
|
||||
return [getOpenCodeConfigDir(options)]
|
||||
}
|
||||
|
||||
const customConfigDir = getCliCustomConfigDir()
|
||||
|
||||
return Array.from(
|
||||
new Set([
|
||||
...(customConfigDir ? [customConfigDir] : []),
|
||||
getCliDefaultConfigDir(),
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
export function getOpenCodeConfigDir(options: OpenCodeConfigDirOptions): string {
|
||||
const { binary, version, checkExisting = true } = options
|
||||
|
||||
|
||||
Reference in New Issue
Block a user