Skip to content

Commit b67ce58

Browse files
Clean install (#69)
* Clean cli * Rename features to background * Fix test * Update src/cli/providers.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update src/cli/system.test.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
1 parent 85b96cd commit b67ce58

21 files changed

Lines changed: 1052 additions & 641 deletions
File renamed without changes.

src/cli/config-io.test.ts

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/// <reference types="bun-types" />
2+
3+
import { describe, expect, test, afterEach, beforeEach, mock } from "bun:test"
4+
import {
5+
stripJsonComments,
6+
parseConfigFile,
7+
parseConfig,
8+
writeConfig,
9+
addPluginToOpenCodeConfig,
10+
addAuthPlugins,
11+
addProviderConfig,
12+
writeLiteConfig,
13+
disableDefaultAgents,
14+
detectCurrentConfig,
15+
} from "./config-io"
16+
import { join } from "node:path"
17+
import { existsSync, rmSync, mkdtempSync, writeFileSync, readFileSync } from "node:fs"
18+
import { tmpdir } from "node:os"
19+
import * as paths from "./paths"
20+
import * as system from "./system"
21+
22+
describe("config-io", () => {
23+
let tmpDir: string
24+
const originalEnv = { ...process.env }
25+
26+
beforeEach(() => {
27+
tmpDir = mkdtempSync(join(tmpdir(), "opencode-io-test-"))
28+
process.env.XDG_CONFIG_HOME = tmpDir
29+
})
30+
31+
afterEach(() => {
32+
process.env = { ...originalEnv }
33+
if (tmpDir && existsSync(tmpDir)) {
34+
rmSync(tmpDir, { recursive: true, force: true })
35+
}
36+
mock.restore()
37+
})
38+
39+
test("stripJsonComments strips comments and trailing commas", () => {
40+
const jsonc = `{
41+
// comment
42+
"a": 1, /* multi
43+
line */
44+
"b": [2,],
45+
}`
46+
const stripped = stripJsonComments(jsonc)
47+
expect(JSON.parse(stripped)).toEqual({ a: 1, b: [2] })
48+
})
49+
50+
test("parseConfigFile parses valid JSON", () => {
51+
const path = join(tmpDir, "test.json")
52+
writeFileSync(path, '{"a": 1}')
53+
const result = parseConfigFile(path)
54+
expect(result.config).toEqual({ a: 1 } as any)
55+
expect(result.error).toBeUndefined()
56+
})
57+
58+
test("parseConfigFile returns null for non-existent file", () => {
59+
const result = parseConfigFile(join(tmpDir, "nonexistent.json"))
60+
expect(result.config).toBeNull()
61+
})
62+
63+
test("parseConfigFile returns null for empty or whitespace-only file", () => {
64+
const emptyPath = join(tmpDir, "empty.json")
65+
writeFileSync(emptyPath, "")
66+
expect(parseConfigFile(emptyPath).config).toBeNull()
67+
68+
const whitespacePath = join(tmpDir, "whitespace.json")
69+
writeFileSync(whitespacePath, " \n ")
70+
expect(parseConfigFile(whitespacePath).config).toBeNull()
71+
})
72+
73+
test("parseConfigFile returns error for invalid JSON", () => {
74+
const path = join(tmpDir, "invalid.json")
75+
writeFileSync(path, '{"a": 1')
76+
const result = parseConfigFile(path)
77+
expect(result.config).toBeNull()
78+
expect(result.error).toBeDefined()
79+
})
80+
81+
test("parseConfig tries .jsonc if .json is missing", () => {
82+
const jsoncPath = join(tmpDir, "test.jsonc")
83+
writeFileSync(jsoncPath, '{"a": 1}')
84+
85+
// We pass .json path, it should try .jsonc
86+
const result = parseConfig(join(tmpDir, "test.json"))
87+
expect(result.config).toEqual({ a: 1 } as any)
88+
})
89+
90+
test("writeConfig writes JSON and creates backup", () => {
91+
const path = join(tmpDir, "test.json")
92+
writeFileSync(path, '{"old": true}')
93+
94+
writeConfig(path, { new: true } as any)
95+
96+
expect(JSON.parse(readFileSync(path, "utf-8"))).toEqual({ new: true })
97+
expect(JSON.parse(readFileSync(path + ".bak", "utf-8"))).toEqual({ old: true })
98+
})
99+
100+
test("addPluginToOpenCodeConfig adds plugin and removes duplicates", async () => {
101+
const configPath = join(tmpDir, "opencode", "opencode.json")
102+
paths.ensureConfigDir()
103+
writeFileSync(configPath, JSON.stringify({ plugin: ["other", "oh-my-opencode-slim@1.0.0"] }))
104+
105+
const result = await addPluginToOpenCodeConfig()
106+
expect(result.success).toBe(true)
107+
108+
const saved = JSON.parse(readFileSync(configPath, "utf-8"))
109+
expect(saved.plugin).toContain("oh-my-opencode-slim")
110+
expect(saved.plugin).not.toContain("oh-my-opencode-slim@1.0.0")
111+
expect(saved.plugin.length).toBe(2)
112+
})
113+
114+
test("addAuthPlugins adds antigravity auth plugin", async () => {
115+
const configPath = join(tmpDir, "opencode", "opencode.json")
116+
paths.ensureConfigDir()
117+
writeFileSync(configPath, JSON.stringify({}))
118+
119+
mock.module("./system", () => ({
120+
fetchLatestVersion: async () => "1.2.3"
121+
}))
122+
123+
const result = await addAuthPlugins({ hasAntigravity: true, hasOpenAI: false, hasOpencodeZen: false, hasTmux: false })
124+
expect(result.success).toBe(true)
125+
126+
const saved = JSON.parse(readFileSync(configPath, "utf-8"))
127+
expect(saved.plugin).toContain("opencode-antigravity-auth@1.2.3")
128+
})
129+
130+
test("addProviderConfig adds google provider config", () => {
131+
const configPath = join(tmpDir, "opencode", "opencode.json")
132+
paths.ensureConfigDir()
133+
writeFileSync(configPath, JSON.stringify({}))
134+
135+
const result = addProviderConfig({ hasAntigravity: true, hasOpenAI: false, hasOpencodeZen: false, hasTmux: false })
136+
expect(result.success).toBe(true)
137+
138+
const saved = JSON.parse(readFileSync(configPath, "utf-8"))
139+
expect(saved.provider.google).toBeDefined()
140+
})
141+
142+
test("writeLiteConfig writes lite config", () => {
143+
const litePath = join(tmpDir, "opencode", "oh-my-opencode-slim.json")
144+
paths.ensureConfigDir()
145+
146+
const result = writeLiteConfig({ hasAntigravity: true, hasOpenAI: false, hasOpencodeZen: false, hasTmux: true })
147+
expect(result.success).toBe(true)
148+
149+
const saved = JSON.parse(readFileSync(litePath, "utf-8"))
150+
expect(saved.agents).toBeDefined()
151+
expect(saved.tmux.enabled).toBe(true)
152+
})
153+
154+
test("disableDefaultAgents disables explore and general agents", () => {
155+
const configPath = join(tmpDir, "opencode", "opencode.json")
156+
paths.ensureConfigDir()
157+
writeFileSync(configPath, JSON.stringify({}))
158+
159+
const result = disableDefaultAgents()
160+
expect(result.success).toBe(true)
161+
162+
const saved = JSON.parse(readFileSync(configPath, "utf-8"))
163+
expect(saved.agent.explore.disable).toBe(true)
164+
expect(saved.agent.general.disable).toBe(true)
165+
})
166+
167+
test("detectCurrentConfig detects installed status", () => {
168+
const configPath = join(tmpDir, "opencode", "opencode.json")
169+
const litePath = join(tmpDir, "opencode", "oh-my-opencode-slim.json")
170+
paths.ensureConfigDir()
171+
172+
writeFileSync(configPath, JSON.stringify({ plugin: ["oh-my-opencode-slim", "opencode-antigravity-auth"] }))
173+
writeFileSync(litePath, JSON.stringify({
174+
agents: { orchestrator: { model: "openai/gpt-4" } },
175+
tmux: { enabled: true }
176+
}))
177+
178+
const detected = detectCurrentConfig()
179+
expect(detected.isInstalled).toBe(true)
180+
expect(detected.hasAntigravity).toBe(true)
181+
expect(detected.hasOpenAI).toBe(true)
182+
expect(detected.hasTmux).toBe(true)
183+
})
184+
})

0 commit comments

Comments
 (0)