Merge pull request #239 from mvanhorn/osc/125-fix-install-config-overwrite

fix(install): merge config instead of overwriting on opencode target
This commit is contained in:
Kieran Klaassen
2026-03-10 16:43:27 -07:00
committed by GitHub
3 changed files with 41 additions and 1 deletions

View File

@@ -18,8 +18,8 @@ export async function mergeJsonConfigAtKey(options: {
const merged = {
...existing,
[key]: {
...existingEntries,
...incoming,
...existingEntries, // existing user entries win on conflict
},
}

View File

@@ -58,12 +58,16 @@ export async function writeOpenCodeBundle(outputRoot: string, bundle: OpenCodeBu
const openCodePaths = resolveOpenCodePaths(outputRoot)
await ensureDir(openCodePaths.root)
const hadExistingConfig = await pathExists(openCodePaths.configPath)
const backupPath = await backupFile(openCodePaths.configPath)
if (backupPath) {
console.log(`Backed up existing config to ${backupPath}`)
}
const merged = await mergeOpenCodeConfig(openCodePaths.configPath, bundle.config)
await writeJson(openCodePaths.configPath, merged)
if (hadExistingConfig) {
console.log("Merged plugin config into existing opencode.json (user settings preserved)")
}
const agentsDir = openCodePaths.agentsDir
for (const agent of bundle.agents) {

View File

@@ -3,6 +3,7 @@ import { promises as fs } from "fs"
import path from "path"
import os from "os"
import { writeOpenCodeBundle } from "../src/targets/opencode"
import { mergeJsonConfigAtKey } from "../src/sync/json-config"
import type { OpenCodeBundle } from "../src/types/opencode"
async function exists(filePath: string): Promise<boolean> {
@@ -254,3 +255,38 @@ describe("writeOpenCodeBundle", () => {
expect(backupContent).toBe("old content\n")
})
})
describe("mergeJsonConfigAtKey", () => {
test("preserves existing user entries on conflict", async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "json-merge-"))
const configPath = path.join(tempDir, "opencode.json")
// User has an existing MCP server config
const existingConfig = {
model: "my-model",
mcp: {
"user-server": { type: "local", command: ["uvx", "user-srv"] },
},
}
await fs.writeFile(configPath, JSON.stringify(existingConfig, null, 2))
// Plugin tries to add its own server and override user-server
await mergeJsonConfigAtKey({
configPath,
key: "mcp",
incoming: {
"plugin-server": { type: "local", command: ["uvx", "plugin-srv"] },
"user-server": { type: "local", command: ["uvx", "plugin-override"] },
},
})
const merged = JSON.parse(await fs.readFile(configPath, "utf8"))
// User's top-level keys preserved
expect(merged.model).toBe("my-model")
// Plugin server added
expect(merged.mcp["plugin-server"]).toBeDefined()
// User's server NOT overwritten by plugin
expect(merged.mcp["user-server"].command[1]).toBe("user-srv")
})
})