forked from code-yeongyu/oh-my-openagent
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathopencode-config-dir.test.ts
More file actions
378 lines (302 loc) · 15.8 KB
/
opencode-config-dir.test.ts
File metadata and controls
378 lines (302 loc) · 15.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
import { describe, test, expect, beforeEach, afterEach } from "bun:test"
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
import { homedir, tmpdir } from "node:os"
import { join, resolve, win32 } from "node:path"
import {
getOpenCodeConfigDir,
getOpenCodeConfigPaths,
isDevBuild,
detectExistingConfigDir,
TAURI_APP_IDENTIFIER,
TAURI_APP_IDENTIFIER_DEV,
} from "./opencode-config-dir"
describe("opencode-config-dir", () => {
let originalPlatform: NodeJS.Platform
let originalEnv: Record<string, string | undefined>
const tempDirs: string[] = []
beforeEach(() => {
originalPlatform = process.platform
originalEnv = {
APPDATA: process.env.APPDATA,
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
XDG_DATA_HOME: process.env.XDG_DATA_HOME,
OPENCODE_CONFIG_DIR: process.env.OPENCODE_CONFIG_DIR,
}
})
afterEach(() => {
Object.defineProperty(process, "platform", { value: originalPlatform })
for (const [key, value] of Object.entries(originalEnv)) {
if (value !== undefined) {
process.env[key] = value
} else {
delete process.env[key]
}
}
for (const dir of tempDirs.splice(0)) {
rmSync(dir, { recursive: true, force: true })
}
})
describe("OPENCODE_CONFIG_DIR environment variable", () => {
test("returns OPENCODE_CONFIG_DIR when env var is set", () => {
// given OPENCODE_CONFIG_DIR is set to a custom path
process.env.OPENCODE_CONFIG_DIR = "/custom/opencode/path"
Object.defineProperty(process, "platform", { value: "linux" })
// when getOpenCodeConfigDir is called with binary="opencode"
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200" })
// then returns the custom path
expect(result).toBe("/custom/opencode/path")
})
test("falls back to default when env var is not set", () => {
// given OPENCODE_CONFIG_DIR is not set, platform is Linux
delete process.env.OPENCODE_CONFIG_DIR
delete process.env.XDG_CONFIG_HOME
Object.defineProperty(process, "platform", { value: "linux" })
// when getOpenCodeConfigDir is called with binary="opencode"
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200" })
// then returns default ~/.config/opencode
expect(result).toBe(join(homedir(), ".config", "opencode"))
})
test("falls back to default when env var is empty string", () => {
// given OPENCODE_CONFIG_DIR is set to empty string
process.env.OPENCODE_CONFIG_DIR = ""
delete process.env.XDG_CONFIG_HOME
Object.defineProperty(process, "platform", { value: "linux" })
// when getOpenCodeConfigDir is called with binary="opencode"
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200" })
// then returns default ~/.config/opencode
expect(result).toBe(join(homedir(), ".config", "opencode"))
})
test("falls back to default when env var is whitespace only", () => {
// given OPENCODE_CONFIG_DIR is set to whitespace only
process.env.OPENCODE_CONFIG_DIR = " "
delete process.env.XDG_CONFIG_HOME
Object.defineProperty(process, "platform", { value: "linux" })
// when getOpenCodeConfigDir is called with binary="opencode"
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200" })
// then returns default ~/.config/opencode
expect(result).toBe(join(homedir(), ".config", "opencode"))
})
test("resolves relative path to absolute path", () => {
// given OPENCODE_CONFIG_DIR is set to a relative path
process.env.OPENCODE_CONFIG_DIR = "./my-opencode-config"
Object.defineProperty(process, "platform", { value: "linux" })
// when getOpenCodeConfigDir is called with binary="opencode"
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200" })
// then returns resolved absolute path
expect(result).toBe(resolve("./my-opencode-config"))
})
test("OPENCODE_CONFIG_DIR takes priority over XDG_CONFIG_HOME", () => {
// given both OPENCODE_CONFIG_DIR and XDG_CONFIG_HOME are set
process.env.OPENCODE_CONFIG_DIR = "/custom/opencode/path"
process.env.XDG_CONFIG_HOME = "/xdg/config"
Object.defineProperty(process, "platform", { value: "linux" })
// when getOpenCodeConfigDir is called with binary="opencode"
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200" })
// then OPENCODE_CONFIG_DIR takes priority
expect(result).toBe("/custom/opencode/path")
})
})
describe("isDevBuild", () => {
test("returns false for null version", () => {
expect(isDevBuild(null)).toBe(false)
})
test("returns false for undefined version", () => {
expect(isDevBuild(undefined)).toBe(false)
})
test("returns false for production version", () => {
expect(isDevBuild("1.0.200")).toBe(false)
expect(isDevBuild("2.1.0")).toBe(false)
})
test("returns true for version containing -dev", () => {
expect(isDevBuild("1.0.0-dev")).toBe(true)
expect(isDevBuild("1.0.0-dev.123")).toBe(true)
})
test("returns true for version containing .dev", () => {
expect(isDevBuild("1.0.0.dev")).toBe(true)
expect(isDevBuild("1.0.0.dev.456")).toBe(true)
})
})
describe("getOpenCodeConfigDir", () => {
describe("for opencode CLI binary", () => {
test("returns ~/.config/opencode on Linux", () => {
// given opencode CLI binary detected, platform is Linux
Object.defineProperty(process, "platform", { value: "linux" })
delete process.env.XDG_CONFIG_HOME
delete process.env.OPENCODE_CONFIG_DIR
// when getOpenCodeConfigDir is called with binary="opencode"
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200" })
// then returns ~/.config/opencode
expect(result).toBe(join(homedir(), ".config", "opencode"))
})
test("returns $XDG_CONFIG_HOME/opencode on Linux when XDG_CONFIG_HOME is set", () => {
// given opencode CLI binary detected, platform is Linux with XDG_CONFIG_HOME set
Object.defineProperty(process, "platform", { value: "linux" })
process.env.XDG_CONFIG_HOME = "/custom/config"
delete process.env.OPENCODE_CONFIG_DIR
// when getOpenCodeConfigDir is called with binary="opencode"
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200" })
// then returns $XDG_CONFIG_HOME/opencode
expect(result).toBe("/custom/config/opencode")
})
test("returns ~/.config/opencode on macOS", () => {
// given opencode CLI binary detected, platform is macOS
Object.defineProperty(process, "platform", { value: "darwin" })
delete process.env.XDG_CONFIG_HOME
delete process.env.OPENCODE_CONFIG_DIR
// when getOpenCodeConfigDir is called with binary="opencode"
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200" })
// then returns ~/.config/opencode
expect(result).toBe(join(homedir(), ".config", "opencode"))
})
test("returns ~/.config/opencode on Windows by default", () => {
// given opencode CLI binary detected, platform is Windows
Object.defineProperty(process, "platform", { value: "win32" })
delete process.env.APPDATA
delete process.env.XDG_CONFIG_HOME
delete process.env.OPENCODE_CONFIG_DIR
// when getOpenCodeConfigDir is called with binary="opencode"
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200", checkExisting: false })
// then returns ~/.config/opencode (cross-platform default)
expect(result).toBe(join(homedir(), ".config", "opencode"))
})
test("returns ~/.config/opencode on Windows even when APPDATA is set (#2502)", () => {
// given opencode CLI binary detected, platform is Windows with APPDATA set
// (regression test: previously would check AppData for existing config)
Object.defineProperty(process, "platform", { value: "win32" })
process.env.APPDATA = "C:\\Users\\TestUser\\AppData\\Roaming"
delete process.env.XDG_CONFIG_HOME
delete process.env.OPENCODE_CONFIG_DIR
// when getOpenCodeConfigDir is called with binary="opencode"
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200", checkExisting: false })
// then returns ~/.config/opencode (ignores APPDATA entirely for CLI)
expect(result).toBe(join(homedir(), ".config", "opencode"))
})
})
describe("for opencode-desktop Tauri binary", () => {
test("returns ~/.config/ai.opencode.desktop on Linux", () => {
// given opencode-desktop binary detected, platform is Linux
Object.defineProperty(process, "platform", { value: "linux" })
delete process.env.XDG_CONFIG_HOME
// when getOpenCodeConfigDir is called with binary="opencode-desktop"
const result = getOpenCodeConfigDir({ binary: "opencode-desktop", version: "1.0.200", checkExisting: false })
// then returns ~/.config/ai.opencode.desktop
expect(result).toBe(join(homedir(), ".config", TAURI_APP_IDENTIFIER))
})
test("returns ~/Library/Application Support/ai.opencode.desktop on macOS", () => {
// given opencode-desktop binary detected, platform is macOS
Object.defineProperty(process, "platform", { value: "darwin" })
// when getOpenCodeConfigDir is called with binary="opencode-desktop"
const result = getOpenCodeConfigDir({ binary: "opencode-desktop", version: "1.0.200", checkExisting: false })
// then returns ~/Library/Application Support/ai.opencode.desktop
expect(result).toBe(join(homedir(), "Library", "Application Support", TAURI_APP_IDENTIFIER))
})
test("returns %APPDATA%/ai.opencode.desktop on Windows", () => {
// given opencode-desktop binary detected, platform is Windows
Object.defineProperty(process, "platform", { value: "win32" })
process.env.APPDATA = "C:\\Users\\TestUser\\AppData\\Roaming"
// when getOpenCodeConfigDir is called with binary="opencode-desktop"
const result = getOpenCodeConfigDir({ binary: "opencode-desktop", version: "1.0.200", checkExisting: false })
// then returns %APPDATA%/ai.opencode.desktop using Windows path semantics
expect(result).toBe(win32.join("C:\\Users\\TestUser\\AppData\\Roaming", TAURI_APP_IDENTIFIER))
})
})
describe("dev build detection", () => {
test("returns ai.opencode.desktop.dev path when dev version detected", () => {
// given opencode-desktop dev version
Object.defineProperty(process, "platform", { value: "linux" })
delete process.env.XDG_CONFIG_HOME
// when getOpenCodeConfigDir is called with dev version
const result = getOpenCodeConfigDir({ binary: "opencode-desktop", version: "1.0.0-dev.123", checkExisting: false })
// then returns path with ai.opencode.desktop.dev
expect(result).toBe(join(homedir(), ".config", TAURI_APP_IDENTIFIER_DEV))
})
test("returns ai.opencode.desktop.dev on macOS for dev build", () => {
// given opencode-desktop dev version on macOS
Object.defineProperty(process, "platform", { value: "darwin" })
// when getOpenCodeConfigDir is called with dev version
const result = getOpenCodeConfigDir({ binary: "opencode-desktop", version: "1.0.0-dev", checkExisting: false })
// then returns path with ai.opencode.desktop.dev
expect(result).toBe(join(homedir(), "Library", "Application Support", TAURI_APP_IDENTIFIER_DEV))
})
})
})
describe("getOpenCodeConfigPaths", () => {
test("returns all config paths for CLI binary", () => {
// given opencode CLI binary on Linux
Object.defineProperty(process, "platform", { value: "linux" })
delete process.env.XDG_CONFIG_HOME
delete process.env.OPENCODE_CONFIG_DIR
// when getOpenCodeConfigPaths is called
const paths = getOpenCodeConfigPaths({ binary: "opencode", version: "1.0.200" })
// then returns all expected paths
const expectedDir = join(homedir(), ".config", "opencode")
expect(paths.configDir).toBe(expectedDir)
expect(paths.configJson).toBe(join(expectedDir, "opencode.json"))
expect(paths.configJsonc).toBe(join(expectedDir, "opencode.jsonc"))
expect(paths.packageJson).toBe(join(expectedDir, "package.json"))
expect(paths.omoConfig).toBe(join(expectedDir, "oh-my-openagent.json"))
})
test("returns all config paths for desktop binary", () => {
// given opencode-desktop binary on macOS
Object.defineProperty(process, "platform", { value: "darwin" })
// when getOpenCodeConfigPaths is called
const paths = getOpenCodeConfigPaths({ binary: "opencode-desktop", version: "1.0.200", checkExisting: false })
// then returns all expected paths
const expectedDir = join(homedir(), "Library", "Application Support", TAURI_APP_IDENTIFIER)
expect(paths.configDir).toBe(expectedDir)
expect(paths.configJson).toBe(join(expectedDir, "opencode.json"))
expect(paths.configJsonc).toBe(join(expectedDir, "opencode.jsonc"))
expect(paths.packageJson).toBe(join(expectedDir, "package.json"))
expect(paths.omoConfig).toBe(join(expectedDir, "oh-my-openagent.json"))
})
test("returns the detected legacy plugin config when it is the only plugin config file", () => {
// given
const configDir = mkdtempSync(join(tmpdir(), "omo-config-paths-legacy-"))
tempDirs.push(configDir)
writeFileSync(join(configDir, "oh-my-opencode.jsonc"), "{}")
process.env.OPENCODE_CONFIG_DIR = configDir
Object.defineProperty(process, "platform", { value: "linux" })
// when
const paths = getOpenCodeConfigPaths({ binary: "opencode", version: "1.0.200" })
// then
expect(paths.omoConfig).toBe(join(configDir, "oh-my-opencode.jsonc"))
})
test("prefers the canonical basename when canonical and legacy plugin config files both exist", () => {
// given
const configDir = mkdtempSync(join(tmpdir(), "omo-config-paths-canonical-"))
tempDirs.push(configDir)
mkdirSync(configDir, { recursive: true })
writeFileSync(join(configDir, "oh-my-opencode.jsonc"), "{}")
writeFileSync(join(configDir, "oh-my-openagent.json"), "{}")
process.env.OPENCODE_CONFIG_DIR = configDir
Object.defineProperty(process, "platform", { value: "linux" })
// when
const paths = getOpenCodeConfigPaths({ binary: "opencode", version: "1.0.200" })
// then
expect(paths.omoConfig).toBe(join(configDir, "oh-my-openagent.json"))
})
})
describe("detectExistingConfigDir", () => {
test("returns null when no config exists", () => {
// given no config files exist
Object.defineProperty(process, "platform", { value: "linux" })
delete process.env.XDG_CONFIG_HOME
delete process.env.OPENCODE_CONFIG_DIR
// when detectExistingConfigDir is called
const result = detectExistingConfigDir("opencode", "1.0.200")
// then result is either null or a valid string path
expect(result === null || typeof result === "string").toBe(true)
})
test("includes OPENCODE_CONFIG_DIR in search locations when set", () => {
// given OPENCODE_CONFIG_DIR is set to a custom path
process.env.OPENCODE_CONFIG_DIR = "/custom/opencode/path"
Object.defineProperty(process, "platform", { value: "linux" })
delete process.env.XDG_CONFIG_HOME
// when detectExistingConfigDir is called
const result = detectExistingConfigDir("opencode", "1.0.200")
// then result is either null (no config file exists) or a valid string path
// The important thing is that the function doesn't throw
expect(result === null || typeof result === "string").toBe(true)
})
})
})