Multiple files were hardcoding ~/.config/opencode paths instead of using getOpenCodeConfigDir() which respects the OPENCODE_CONFIG_DIR env var. This broke profile isolation features like OCX ghost mode, where users set OPENCODE_CONFIG_DIR to a custom path but oh-my-opencode.json and other configs weren't being read from that location. Changes: - plugin-config.ts: Use getOpenCodeConfigDir() directly - cli/doctor/checks: Use getOpenCodeConfigDir() for auth and config checks - tools/lsp/config.ts: Use getOpenCodeConfigDir() for LSP config paths - command loaders: Use getOpenCodeConfigDir() for global command dirs - hooks: Use getOpenCodeConfigDir() for hook config paths - config-path.ts: Mark getUserConfigDir() as deprecated - tests: Ensure OPENCODE_CONFIG_DIR is properly isolated in tests
123 lines
3.3 KiB
TypeScript
123 lines
3.3 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs"
|
|
import { join } from "node:path"
|
|
import type { CheckResult, CheckDefinition, ConfigInfo } from "../types"
|
|
import { CHECK_IDS, CHECK_NAMES, PACKAGE_NAME } from "../constants"
|
|
import { parseJsonc, detectConfigFile, getOpenCodeConfigDir } from "../../../shared"
|
|
import { OhMyOpenCodeConfigSchema } from "../../../config"
|
|
|
|
const USER_CONFIG_DIR = getOpenCodeConfigDir({ binary: "opencode" })
|
|
const USER_CONFIG_BASE = join(USER_CONFIG_DIR, `${PACKAGE_NAME}`)
|
|
const PROJECT_CONFIG_BASE = join(process.cwd(), ".opencode", PACKAGE_NAME)
|
|
|
|
function findConfigPath(): { path: string; format: "json" | "jsonc" } | null {
|
|
const projectDetected = detectConfigFile(PROJECT_CONFIG_BASE)
|
|
if (projectDetected.format !== "none") {
|
|
return { path: projectDetected.path, format: projectDetected.format as "json" | "jsonc" }
|
|
}
|
|
|
|
const userDetected = detectConfigFile(USER_CONFIG_BASE)
|
|
if (userDetected.format !== "none") {
|
|
return { path: userDetected.path, format: userDetected.format as "json" | "jsonc" }
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
export function validateConfig(configPath: string): { valid: boolean; errors: string[] } {
|
|
try {
|
|
const content = readFileSync(configPath, "utf-8")
|
|
const rawConfig = parseJsonc<Record<string, unknown>>(content)
|
|
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig)
|
|
|
|
if (!result.success) {
|
|
const errors = result.error.issues.map(
|
|
(i) => `${i.path.join(".")}: ${i.message}`
|
|
)
|
|
return { valid: false, errors }
|
|
}
|
|
|
|
return { valid: true, errors: [] }
|
|
} catch (err) {
|
|
return {
|
|
valid: false,
|
|
errors: [err instanceof Error ? err.message : "Failed to parse config"],
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getConfigInfo(): ConfigInfo {
|
|
const configPath = findConfigPath()
|
|
|
|
if (!configPath) {
|
|
return {
|
|
exists: false,
|
|
path: null,
|
|
format: null,
|
|
valid: true,
|
|
errors: [],
|
|
}
|
|
}
|
|
|
|
if (!existsSync(configPath.path)) {
|
|
return {
|
|
exists: false,
|
|
path: configPath.path,
|
|
format: configPath.format,
|
|
valid: true,
|
|
errors: [],
|
|
}
|
|
}
|
|
|
|
const validation = validateConfig(configPath.path)
|
|
|
|
return {
|
|
exists: true,
|
|
path: configPath.path,
|
|
format: configPath.format,
|
|
valid: validation.valid,
|
|
errors: validation.errors,
|
|
}
|
|
}
|
|
|
|
export async function checkConfigValidity(): Promise<CheckResult> {
|
|
const info = getConfigInfo()
|
|
|
|
if (!info.exists) {
|
|
return {
|
|
name: CHECK_NAMES[CHECK_IDS.CONFIG_VALIDATION],
|
|
status: "pass",
|
|
message: "Using default configuration",
|
|
details: ["No custom config file found (optional)"],
|
|
}
|
|
}
|
|
|
|
if (!info.valid) {
|
|
return {
|
|
name: CHECK_NAMES[CHECK_IDS.CONFIG_VALIDATION],
|
|
status: "fail",
|
|
message: "Configuration has validation errors",
|
|
details: [
|
|
`Path: ${info.path}`,
|
|
...info.errors.map((e) => `Error: ${e}`),
|
|
],
|
|
}
|
|
}
|
|
|
|
return {
|
|
name: CHECK_NAMES[CHECK_IDS.CONFIG_VALIDATION],
|
|
status: "pass",
|
|
message: `Valid ${info.format?.toUpperCase()} config`,
|
|
details: [`Path: ${info.path}`],
|
|
}
|
|
}
|
|
|
|
export function getConfigCheckDefinition(): CheckDefinition {
|
|
return {
|
|
id: CHECK_IDS.CONFIG_VALIDATION,
|
|
name: CHECK_NAMES[CHECK_IDS.CONFIG_VALIDATION],
|
|
category: "configuration",
|
|
check: checkConfigValidity,
|
|
critical: false,
|
|
}
|
|
}
|