diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts index 77643001..fa77ca77 100644 --- a/packages/opencode/src/cli/error.ts +++ b/packages/opencode/src/cli/error.ts @@ -12,7 +12,7 @@ export function FormatError(input: unknown) { } if (Config.InvalidError.isInstance(input)) return [ - `Config file at ${input.data.path} is invalid`, + `Config file at ${input.data.path} is invalid` + (input.data.message ? `: ${input.data.message}` : ""), ...(input.data.issues?.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")) ?? []), ].join("\n") diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 8a373d6f..f8acac39 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -429,14 +429,14 @@ export namespace Config { return load(text, filepath) } - async function load(text: string, filepath: string) { + async function load(text: string, configFilepath: string) { text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => { return process.env[varName] || "" }) const fileMatches = text.match(/\{file:[^}]+\}/g) if (fileMatches) { - const configDir = path.dirname(filepath) + const configDir = path.dirname(configFilepath) const lines = text.split("\n") for (const match of fileMatches) { @@ -449,7 +449,20 @@ export namespace Config { filePath = path.join(os.homedir(), filePath.slice(2)) } const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath) - const fileContent = (await Bun.file(resolvedPath).text()).trim() + const fileContent = ( + await Bun.file(resolvedPath) + .text() + .catch((error) => { + const errMsg = `bad file reference: "${match}"` + if (error.code === "ENOENT") { + throw new InvalidError( + { path: configFilepath, message: errMsg + ` ${resolvedPath} does not exist` }, + { cause: error }, + ) + } + throw new InvalidError({ path: configFilepath, message: errMsg }, { cause: error }) + }) + ).trim() // escape newlines/quotes, strip outer quotes text = text.replace(match, JSON.stringify(fileContent).slice(1, -1)) } @@ -474,7 +487,7 @@ export namespace Config { .join("\n") throw new JsonError({ - path: filepath, + path: configFilepath, message: `\n--- JSONC Input ---\n${text}\n--- Errors ---\n${errorDetails}\n--- End ---`, }) } @@ -483,21 +496,21 @@ export namespace Config { if (parsed.success) { if (!parsed.data.$schema) { parsed.data.$schema = "https://opencode.ai/config.json" - await Bun.write(filepath, JSON.stringify(parsed.data, null, 2)) + await Bun.write(configFilepath, JSON.stringify(parsed.data, null, 2)) } const data = parsed.data if (data.plugin) { for (let i = 0; i < data.plugin?.length; i++) { const plugin = data.plugin[i] try { - data.plugin[i] = import.meta.resolve(plugin, filepath) + data.plugin[i] = import.meta.resolve(plugin, configFilepath) } catch (err) {} } } return data } - throw new InvalidError({ path: filepath, issues: parsed.error.issues }) + throw new InvalidError({ path: configFilepath, issues: parsed.error.issues }) } export const JsonError = NamedError.create( "ConfigJsonError", @@ -512,6 +525,7 @@ export namespace Config { z.object({ path: z.string(), issues: z.custom().optional(), + message: z.string().optional(), }), )