fix: better frontmatter errors

This commit is contained in:
Aiden Cline
2025-10-30 10:56:30 -05:00
parent 2d5df3ad76
commit 42e0b47a7d
3 changed files with 43 additions and 10 deletions

View File

@@ -1,3 +1,4 @@
import { ConfigMarkdown } from "@/config/markdown"
import { Config } from "../config/config"
import { MCP } from "../mcp"
import { UI } from "./ui"
@@ -7,16 +8,22 @@ export function FormatError(input: unknown) {
return `MCP server "${input.data.name}" failed. Note, opencode does not support MCP authentication yet.`
if (Config.JsonError.isInstance(input)) {
return (
`Config file at ${input.data.path} is not valid JSON(C)` + (input.data.message ? `: ${input.data.message}` : "")
`Config file at ${input.data.path} is not valid JSON(C)` +
(input.data.message ? `: ${input.data.message}` : "")
)
}
if (Config.ConfigDirectoryTypoError.isInstance(input)) {
return `Directory "${input.data.dir}" in ${input.data.path} is not valid. Use "${input.data.suggestion}" instead. This is a common typo.`
}
if (ConfigMarkdown.FrontmatterError.isInstance(input)) {
return `Failed to parse frontmatter in ${input.data.path}:\n${input.data.message}`
}
if (Config.InvalidError.isInstance(input))
return [
`Config file at ${input.data.path} is invalid` + (input.data.message ? `: ${input.data.message}` : ""),
...(input.data.issues?.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")) ?? []),
`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")
if (UI.CancelledError.isInstance(input)) return ""

View File

@@ -9,7 +9,6 @@ import { Global } from "../global"
import fs from "fs/promises"
import { lazy } from "../util/lazy"
import { NamedError } from "../util/error"
import matter from "gray-matter"
import { Flag } from "../flag/flag"
import { Auth } from "../auth"
import {
@@ -21,6 +20,7 @@ import { Instance } from "../project/instance"
import { LSPServer } from "../lsp/server"
import { BunProc } from "@/bun"
import { Installation } from "@/installation"
import { ConfigMarkdown } from "./markdown"
export namespace Config {
const log = Log.create({ service: "config" })
@@ -191,8 +191,7 @@ export namespace Config {
dot: true,
cwd: dir,
})) {
const content = await Bun.file(item).text()
const md = matter(content)
const md = await ConfigMarkdown.parse(item)
if (!md.data) continue
const name = (() => {
@@ -231,8 +230,7 @@ export namespace Config {
dot: true,
cwd: dir,
})) {
const content = await Bun.file(item).text()
const md = matter(content)
const md = await ConfigMarkdown.parse(item)
if (!md.data) continue
// Extract relative path from agent folder for nested agents
@@ -274,8 +272,7 @@ export namespace Config {
dot: true,
cwd: dir,
})) {
const content = await Bun.file(item).text()
const md = matter(content)
const md = await ConfigMarkdown.parse(item)
if (!md.data) continue
const config = {

View File

@@ -1,3 +1,7 @@
import { NamedError } from "@/util/error"
import matter from "gray-matter"
import { z } from "zod"
export namespace ConfigMarkdown {
export const FILE_REGEX = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g
export const SHELL_REGEX = /!`([^`]+)`/g
@@ -9,4 +13,29 @@ export namespace ConfigMarkdown {
export function shell(template: string) {
return Array.from(template.matchAll(SHELL_REGEX))
}
export async function parse(filePath: string) {
const template = await Bun.file(filePath).text()
try {
const md = matter(template)
return md
} catch (err) {
throw new FrontmatterError(
{
path: filePath,
message: `Failed to parse YAML frontmatter: ${err instanceof Error ? err.message : String(err)}`,
},
{ cause: err },
)
}
}
export const FrontmatterError = NamedError.create(
"ConfigFrontmatterError",
z.object({
path: z.string(),
message: z.string(),
}),
)
}