From 9b52d33889482d52fc2ccb86a48cfdcade21157b Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 9 Oct 2025 22:40:23 -0400 Subject: [PATCH] core: improve directory validation error messages to help users fix invalid directory names --- packages/opencode/src/cli/error.ts | 2 ++ packages/opencode/src/config/config.ts | 40 +++++++++++++++----------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts index fa77ca773..8288de1eb 100644 --- a/packages/opencode/src/cli/error.ts +++ b/packages/opencode/src/cli/error.ts @@ -10,6 +10,8 @@ export function FormatError(input: unknown) { `Config file at ${input.data.path} is not valid JSON(C)` + (input.data.message ? `: ${input.data.message}` : "") ) } + if (Config.DirectoryError.isInstance(input)) + return `Directory "${input.data.dir}" in ${input.data.path} is not valid. Did you mean "${input.data.suggestion}"?` if (Config.InvalidError.isInstance(input)) return [ `Config file at ${input.data.path} is invalid` + (input.data.message ? `: ${input.data.message}` : ""), diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 234d14573..a05d159e3 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -62,7 +62,7 @@ export namespace Config { ] for (const dir of directories) { - await assertValid(dir).catch(() => {}) + await assertValid(dir) installDependencies(dir) result.command = mergeDeep(result.command ?? {}, await loadCommand(dir)) result.agent = mergeDeep(result.agent, await loadAgent(dir)) @@ -120,23 +120,20 @@ export namespace Config { } }) + const INVALID_DIRS = new Bun.Glob(`{${["agents", "commands", "plugins", "tools"].join(",")}}/`) async function assertValid(dir: string) { - const ALLOWED_DIRS = new Set(["agent", "command", "mode", "plugin", "tool", "themes"]) - const UNEXPECTED_DIR_GLOB = new Bun.Glob("*/") - for await (const item of UNEXPECTED_DIR_GLOB.scan({ - absolute: true, - followSymlinks: true, - dot: true, - cwd: dir, - onlyFiles: false, - })) { - const dirname = path.basename(item) - if (!ALLOWED_DIRS.has(dirname)) { - throw new InvalidError({ - path: dir, - message: `Unexpected directory "${dirname}" found in "${dir}". Only ${ALLOWED_DIRS.values().toArray().join(", ")} directories are allowed.`, - }) - } + const invalid = await Array.fromAsync( + INVALID_DIRS.scan({ + onlyFiles: false, + cwd: dir, + }), + ) + for (const item of invalid) { + throw new DirectoryError({ + path: dir, + dir: item, + suggestion: item.substring(0, item.length - 1), + }) } } @@ -714,6 +711,15 @@ export namespace Config { }), ) + export const DirectoryError = NamedError.create( + "ConfigDirectoryError", + z.object({ + path: z.string(), + dir: z.string(), + suggestion: z.string(), + }), + ) + export const InvalidError = NamedError.create( "ConfigInvalidError", z.object({