fix: better error message when config has invalid references (#1874)
This commit is contained in:
@@ -12,7 +12,7 @@ export function FormatError(input: unknown) {
|
|||||||
}
|
}
|
||||||
if (Config.InvalidError.isInstance(input))
|
if (Config.InvalidError.isInstance(input))
|
||||||
return [
|
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(".")) ?? []),
|
...(input.data.issues?.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")) ?? []),
|
||||||
].join("\n")
|
].join("\n")
|
||||||
|
|
||||||
|
|||||||
@@ -429,14 +429,14 @@ export namespace Config {
|
|||||||
return load(text, filepath)
|
return load(text, filepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load(text: string, filepath: string) {
|
async function load(text: string, configFilepath: string) {
|
||||||
text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => {
|
text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => {
|
||||||
return process.env[varName] || ""
|
return process.env[varName] || ""
|
||||||
})
|
})
|
||||||
|
|
||||||
const fileMatches = text.match(/\{file:[^}]+\}/g)
|
const fileMatches = text.match(/\{file:[^}]+\}/g)
|
||||||
if (fileMatches) {
|
if (fileMatches) {
|
||||||
const configDir = path.dirname(filepath)
|
const configDir = path.dirname(configFilepath)
|
||||||
const lines = text.split("\n")
|
const lines = text.split("\n")
|
||||||
|
|
||||||
for (const match of fileMatches) {
|
for (const match of fileMatches) {
|
||||||
@@ -449,7 +449,20 @@ export namespace Config {
|
|||||||
filePath = path.join(os.homedir(), filePath.slice(2))
|
filePath = path.join(os.homedir(), filePath.slice(2))
|
||||||
}
|
}
|
||||||
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath)
|
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
|
// escape newlines/quotes, strip outer quotes
|
||||||
text = text.replace(match, JSON.stringify(fileContent).slice(1, -1))
|
text = text.replace(match, JSON.stringify(fileContent).slice(1, -1))
|
||||||
}
|
}
|
||||||
@@ -474,7 +487,7 @@ export namespace Config {
|
|||||||
.join("\n")
|
.join("\n")
|
||||||
|
|
||||||
throw new JsonError({
|
throw new JsonError({
|
||||||
path: filepath,
|
path: configFilepath,
|
||||||
message: `\n--- JSONC Input ---\n${text}\n--- Errors ---\n${errorDetails}\n--- End ---`,
|
message: `\n--- JSONC Input ---\n${text}\n--- Errors ---\n${errorDetails}\n--- End ---`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -483,21 +496,21 @@ export namespace Config {
|
|||||||
if (parsed.success) {
|
if (parsed.success) {
|
||||||
if (!parsed.data.$schema) {
|
if (!parsed.data.$schema) {
|
||||||
parsed.data.$schema = "https://opencode.ai/config.json"
|
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
|
const data = parsed.data
|
||||||
if (data.plugin) {
|
if (data.plugin) {
|
||||||
for (let i = 0; i < data.plugin?.length; i++) {
|
for (let i = 0; i < data.plugin?.length; i++) {
|
||||||
const plugin = data.plugin[i]
|
const plugin = data.plugin[i]
|
||||||
try {
|
try {
|
||||||
data.plugin[i] = import.meta.resolve(plugin, filepath)
|
data.plugin[i] = import.meta.resolve(plugin, configFilepath)
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return data
|
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(
|
export const JsonError = NamedError.create(
|
||||||
"ConfigJsonError",
|
"ConfigJsonError",
|
||||||
@@ -512,6 +525,7 @@ export namespace Config {
|
|||||||
z.object({
|
z.object({
|
||||||
path: z.string(),
|
path: z.string(),
|
||||||
issues: z.custom<z.ZodIssue[]>().optional(),
|
issues: z.custom<z.ZodIssue[]>().optional(),
|
||||||
|
message: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user