diff --git a/packages/opencode/src/config/paths.ts b/packages/opencode/src/config/paths.ts index e9f2241776..b67c96416e 100644 --- a/packages/opencode/src/config/paths.ts +++ b/packages/opencode/src/config/paths.ts @@ -77,17 +77,27 @@ export namespace ConfigPaths { return process.env[varName] || "" }) - const fileMatches = text.match(/\{file:[^}]+\}/g) - if (!fileMatches) return text + const fileMatches = Array.from(text.matchAll(/\{file:[^}]+\}/g)) + if (!fileMatches.length) return text const configDir = path.dirname(configFilepath) - const lines = text.split("\n") + let out = "" + let cursor = 0 for (const match of fileMatches) { - const lineIndex = lines.findIndex((line) => line.includes(match)) - if (lineIndex !== -1 && lines[lineIndex].trim().startsWith("//")) continue + const token = match[0] + const index = match.index! + out += text.slice(cursor, index) - let filePath = match.replace(/^\{file:/, "").replace(/\}$/, "") + const lineStart = text.lastIndexOf("\n", index - 1) + 1 + const prefix = text.slice(lineStart, index).trimStart() + if (prefix.startsWith("//")) { + out += token + cursor = index + token.length + continue + } + + let filePath = token.replace(/^\{file:/, "").replace(/\}$/, "") if (filePath.startsWith("~/")) { filePath = path.join(os.homedir(), filePath.slice(2)) } @@ -99,7 +109,7 @@ export namespace ConfigPaths { .catch((error) => { if (missing === "empty") return "" - const errMsg = `bad file reference: "${match}"` + const errMsg = `bad file reference: "${token}"` if (error.code === "ENOENT") { throw new InvalidError( { @@ -113,10 +123,12 @@ export namespace ConfigPaths { }) ).trim() - text = text.replace(match, () => JSON.stringify(fileContent).slice(1, -1)) + out += JSON.stringify(fileContent).slice(1, -1) + cursor = index + token.length } - return text + out += text.slice(cursor) + return out } /** Substitute and parse JSONC text, throwing JsonError on syntax errors. */ diff --git a/packages/opencode/test/config/tui.test.ts b/packages/opencode/test/config/tui.test.ts index 2a98853dfb..0f4e9d3b1e 100644 --- a/packages/opencode/test/config/tui.test.ts +++ b/packages/opencode/test/config/tui.test.ts @@ -384,6 +384,29 @@ test("applies env and file substitutions in tui.json", async () => { } }) +test("applies file substitutions when first identical token is in a commented line", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write(path.join(dir, "theme.txt"), "resolved-theme") + await Bun.write( + path.join(dir, "tui.jsonc"), + `{ + // "theme": "{file:theme.txt}", + "theme": "{file:theme.txt}" +}`, + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await TuiConfig.get() + expect(config.theme).toBe("resolved-theme") + }, + }) +}) + test("loads managed tui config and gives it highest precedence", async () => { await using tmp = await tmpdir({ init: async (dir) => {