| 1005 | } |
| 1006 | |
| 1007 | async function load(text: string, configFilepath: string) { |
| 1008 | text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => { |
| 1009 | return process.env[varName] || "" |
| 1010 | }) |
| 1011 | |
| 1012 | const fileMatches = text.match(/\{file:[^}]+\}/g) |
| 1013 | if (fileMatches) { |
| 1014 | const configDir = path.dirname(configFilepath) |
| 1015 | const lines = text.split("\n") |
| 1016 | |
| 1017 | for (const match of fileMatches) { |
| 1018 | const lineIndex = lines.findIndex((line) => line.includes(match)) |
| 1019 | if (lineIndex !== -1 && lines[lineIndex].trim().startsWith("//")) { |
| 1020 | continue // Skip if line is commented |
| 1021 | } |
| 1022 | let filePath = match.replace(/^\{file:/, "").replace(/\}$/, "") |
| 1023 | if (filePath.startsWith("~/")) { |
| 1024 | filePath = path.join(os.homedir(), filePath.slice(2)) |
| 1025 | } |
| 1026 | const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath) |
| 1027 | const fileContent = ( |
| 1028 | await Bun.file(resolvedPath) |
| 1029 | .text() |
| 1030 | .catch((error) => { |
| 1031 | const errMsg = `bad file reference: "${match}"` |
| 1032 | if (error.code === "ENOENT") { |
| 1033 | throw new InvalidError( |
| 1034 | { |
| 1035 | path: configFilepath, |
| 1036 | message: errMsg + ` ${resolvedPath} does not exist`, |
| 1037 | }, |
| 1038 | { cause: error }, |
| 1039 | ) |
| 1040 | } |
| 1041 | throw new InvalidError({ path: configFilepath, message: errMsg }, { cause: error }) |
| 1042 | }) |
| 1043 | ).trim() |
| 1044 | // escape newlines/quotes, strip outer quotes |
| 1045 | text = text.replace(match, JSON.stringify(fileContent).slice(1, -1)) |
| 1046 | } |
| 1047 | } |
| 1048 | |
| 1049 | const errors: JsoncParseError[] = [] |
| 1050 | const data = parseJsonc(text, errors, { allowTrailingComma: true }) |
| 1051 | if (errors.length) { |
| 1052 | const lines = text.split("\n") |
| 1053 | const errorDetails = errors |
| 1054 | .map((e) => { |
| 1055 | const beforeOffset = text.substring(0, e.offset).split("\n") |
| 1056 | const line = beforeOffset.length |
| 1057 | const column = beforeOffset[beforeOffset.length - 1].length + 1 |
| 1058 | const problemLine = lines[line - 1] |
| 1059 | |
| 1060 | const error = `${printParseErrorCode(e.error)} at line ${line}, column ${column}` |
| 1061 | if (!problemLine) return error |
| 1062 | |
| 1063 | return `${error}\n Line ${line}: ${problemLine}\n${"".padStart(column + 9)}^` |
| 1064 | }) |