opencode-morphllm 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitleaks.toml +6 -0
- package/.husky/pre-commit +3 -1
- package/.prettierignore +1 -0
- package/.prettierrc +1 -1
- package/README.md +7 -2
- package/bun.lock +23 -0
- package/bunfig.toml +4 -0
- package/dist/index.js +11 -12
- package/dist/morph/mcps.js +11 -11
- package/dist/morph/mcps.test.js +32 -32
- package/dist/morph/router.d.ts +16 -21
- package/dist/morph/router.js +51 -55
- package/dist/morph/router.test.js +230 -231
- package/dist/shared/config.d.ts +16 -18
- package/dist/shared/config.js +62 -64
- package/dist/shared/config.test.js +183 -186
- package/dist/shared/opencode-config-dir.d.ts +18 -8
- package/dist/shared/opencode-config-dir.js +93 -47
- package/dist/shared/opencode-config-dir.test.d.ts +1 -0
- package/dist/shared/opencode-config-dir.test.js +310 -0
- package/package.json +3 -2
- package/src/index.ts +1 -2
- package/src/morph/router.test.ts +3 -2
- package/src/morph/router.ts +8 -3
- package/src/shared/config.test.ts +18 -6
- package/src/shared/config.ts +5 -5
- package/src/shared/opencode-config-dir.test.ts +404 -0
- package/src/shared/opencode-config-dir.ts +90 -11
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
getOpenCodeConfigDir,
|
|
6
|
+
getOpenCodeConfigPaths,
|
|
7
|
+
isDevBuild,
|
|
8
|
+
detectExistingConfigDir,
|
|
9
|
+
TAURI_APP_IDENTIFIER,
|
|
10
|
+
TAURI_APP_IDENTIFIER_DEV,
|
|
11
|
+
} from './opencode-config-dir';
|
|
12
|
+
|
|
13
|
+
describe('opencode-config-dir', () => {
|
|
14
|
+
let originalPlatform: NodeJS.Platform;
|
|
15
|
+
let originalEnv: Record<string, string | undefined>;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
originalPlatform = process.platform;
|
|
19
|
+
originalEnv = {
|
|
20
|
+
APPDATA: process.env.APPDATA,
|
|
21
|
+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
|
|
22
|
+
XDG_DATA_HOME: process.env.XDG_DATA_HOME,
|
|
23
|
+
OPENCODE_CONFIG_DIR: process.env.OPENCODE_CONFIG_DIR,
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
Object.defineProperty(process, 'platform', { value: originalPlatform });
|
|
29
|
+
for (const [key, value] of Object.entries(originalEnv)) {
|
|
30
|
+
if (value !== undefined) {
|
|
31
|
+
process.env[key] = value;
|
|
32
|
+
} else {
|
|
33
|
+
delete process.env[key];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('OPENCODE_CONFIG_DIR environment variable', () => {
|
|
39
|
+
test('returns OPENCODE_CONFIG_DIR when env var is set', () => {
|
|
40
|
+
// #given OPENCODE_CONFIG_DIR is set to a custom path
|
|
41
|
+
process.env.OPENCODE_CONFIG_DIR = '/custom/opencode/path';
|
|
42
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
43
|
+
|
|
44
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
45
|
+
const result = getOpenCodeConfigDir({
|
|
46
|
+
binary: 'opencode',
|
|
47
|
+
version: '1.0.200',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// #then returns the custom path
|
|
51
|
+
expect(result).toBe('/custom/opencode/path');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('falls back to default when env var is not set', () => {
|
|
55
|
+
// #given OPENCODE_CONFIG_DIR is not set, platform is Linux
|
|
56
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
57
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
58
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
59
|
+
|
|
60
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
61
|
+
const result = getOpenCodeConfigDir({
|
|
62
|
+
binary: 'opencode',
|
|
63
|
+
version: '1.0.200',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// #then returns default ~/.config/opencode
|
|
67
|
+
expect(result).toBe(join(homedir(), '.config', 'opencode'));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('falls back to default when env var is empty string', () => {
|
|
71
|
+
// #given OPENCODE_CONFIG_DIR is set to empty string
|
|
72
|
+
process.env.OPENCODE_CONFIG_DIR = '';
|
|
73
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
74
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
75
|
+
|
|
76
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
77
|
+
const result = getOpenCodeConfigDir({
|
|
78
|
+
binary: 'opencode',
|
|
79
|
+
version: '1.0.200',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// #then returns default ~/.config/opencode
|
|
83
|
+
expect(result).toBe(join(homedir(), '.config', 'opencode'));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('falls back to default when env var is whitespace only', () => {
|
|
87
|
+
// #given OPENCODE_CONFIG_DIR is set to whitespace only
|
|
88
|
+
process.env.OPENCODE_CONFIG_DIR = ' ';
|
|
89
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
90
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
91
|
+
|
|
92
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
93
|
+
const result = getOpenCodeConfigDir({
|
|
94
|
+
binary: 'opencode',
|
|
95
|
+
version: '1.0.200',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// #then returns default ~/.config/opencode
|
|
99
|
+
expect(result).toBe(join(homedir(), '.config', 'opencode'));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('resolves relative path to absolute path', () => {
|
|
103
|
+
// #given OPENCODE_CONFIG_DIR is set to a relative path
|
|
104
|
+
process.env.OPENCODE_CONFIG_DIR = './my-opencode-config';
|
|
105
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
106
|
+
|
|
107
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
108
|
+
const result = getOpenCodeConfigDir({
|
|
109
|
+
binary: 'opencode',
|
|
110
|
+
version: '1.0.200',
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// #then returns resolved absolute path
|
|
114
|
+
expect(result).toBe(resolve('./my-opencode-config'));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('OPENCODE_CONFIG_DIR takes priority over XDG_CONFIG_HOME', () => {
|
|
118
|
+
// #given both OPENCODE_CONFIG_DIR and XDG_CONFIG_HOME are set
|
|
119
|
+
process.env.OPENCODE_CONFIG_DIR = '/custom/opencode/path';
|
|
120
|
+
process.env.XDG_CONFIG_HOME = '/xdg/config';
|
|
121
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
122
|
+
|
|
123
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
124
|
+
const result = getOpenCodeConfigDir({
|
|
125
|
+
binary: 'opencode',
|
|
126
|
+
version: '1.0.200',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// #then OPENCODE_CONFIG_DIR takes priority
|
|
130
|
+
expect(result).toBe('/custom/opencode/path');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('isDevBuild', () => {
|
|
135
|
+
test('returns false for null version', () => {
|
|
136
|
+
expect(isDevBuild(null)).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('returns false for undefined version', () => {
|
|
140
|
+
expect(isDevBuild(undefined)).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('returns false for production version', () => {
|
|
144
|
+
expect(isDevBuild('1.0.200')).toBe(false);
|
|
145
|
+
expect(isDevBuild('2.1.0')).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('returns true for version containing -dev', () => {
|
|
149
|
+
expect(isDevBuild('1.0.0-dev')).toBe(true);
|
|
150
|
+
expect(isDevBuild('1.0.0-dev.123')).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('returns true for version containing .dev', () => {
|
|
154
|
+
expect(isDevBuild('1.0.0.dev')).toBe(true);
|
|
155
|
+
expect(isDevBuild('1.0.0.dev.456')).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('getOpenCodeConfigDir', () => {
|
|
160
|
+
describe('for opencode CLI binary', () => {
|
|
161
|
+
test('returns ~/.config/opencode on Linux', () => {
|
|
162
|
+
// #given opencode CLI binary detected, platform is Linux
|
|
163
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
164
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
165
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
166
|
+
|
|
167
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
168
|
+
const result = getOpenCodeConfigDir({
|
|
169
|
+
binary: 'opencode',
|
|
170
|
+
version: '1.0.200',
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// #then returns ~/.config/opencode
|
|
174
|
+
expect(result).toBe(join(homedir(), '.config', 'opencode'));
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('returns $XDG_CONFIG_HOME/opencode on Linux when XDG_CONFIG_HOME is set', () => {
|
|
178
|
+
// #given opencode CLI binary detected, platform is Linux with XDG_CONFIG_HOME set
|
|
179
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
180
|
+
process.env.XDG_CONFIG_HOME = '/custom/config';
|
|
181
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
182
|
+
|
|
183
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
184
|
+
const result = getOpenCodeConfigDir({
|
|
185
|
+
binary: 'opencode',
|
|
186
|
+
version: '1.0.200',
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// #then returns $XDG_CONFIG_HOME/opencode
|
|
190
|
+
expect(result).toBe('/custom/config/opencode');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('returns ~/.config/opencode on macOS', () => {
|
|
194
|
+
// #given opencode CLI binary detected, platform is macOS
|
|
195
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
196
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
197
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
198
|
+
|
|
199
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
200
|
+
const result = getOpenCodeConfigDir({
|
|
201
|
+
binary: 'opencode',
|
|
202
|
+
version: '1.0.200',
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// #then returns ~/.config/opencode
|
|
206
|
+
expect(result).toBe(join(homedir(), '.config', 'opencode'));
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('returns ~/.config/opencode on Windows by default', () => {
|
|
210
|
+
// #given opencode CLI binary detected, platform is Windows
|
|
211
|
+
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
212
|
+
delete process.env.APPDATA;
|
|
213
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
214
|
+
|
|
215
|
+
// #when getOpenCodeConfigDir is called with binary="opencode"
|
|
216
|
+
const result = getOpenCodeConfigDir({
|
|
217
|
+
binary: 'opencode',
|
|
218
|
+
version: '1.0.200',
|
|
219
|
+
checkExisting: false,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// #then returns ~/.config/opencode (cross-platform default)
|
|
223
|
+
expect(result).toBe(join(homedir(), '.config', 'opencode'));
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('for opencode-desktop Tauri binary', () => {
|
|
228
|
+
test('returns ~/.config/ai.opencode.desktop on Linux', () => {
|
|
229
|
+
// #given opencode-desktop binary detected, platform is Linux
|
|
230
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
231
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
232
|
+
|
|
233
|
+
// #when getOpenCodeConfigDir is called with binary="opencode-desktop"
|
|
234
|
+
const result = getOpenCodeConfigDir({
|
|
235
|
+
binary: 'opencode-desktop',
|
|
236
|
+
version: '1.0.200',
|
|
237
|
+
checkExisting: false,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// #then returns ~/.config/ai.opencode.desktop
|
|
241
|
+
expect(result).toBe(join(homedir(), '.config', TAURI_APP_IDENTIFIER));
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('returns ~/Library/Application Support/ai.opencode.desktop on macOS', () => {
|
|
245
|
+
// #given opencode-desktop binary detected, platform is macOS
|
|
246
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
247
|
+
|
|
248
|
+
// #when getOpenCodeConfigDir is called with binary="opencode-desktop"
|
|
249
|
+
const result = getOpenCodeConfigDir({
|
|
250
|
+
binary: 'opencode-desktop',
|
|
251
|
+
version: '1.0.200',
|
|
252
|
+
checkExisting: false,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// #then returns ~/Library/Application Support/ai.opencode.desktop
|
|
256
|
+
expect(result).toBe(
|
|
257
|
+
join(
|
|
258
|
+
homedir(),
|
|
259
|
+
'Library',
|
|
260
|
+
'Application Support',
|
|
261
|
+
TAURI_APP_IDENTIFIER
|
|
262
|
+
)
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('returns %APPDATA%/ai.opencode.desktop on Windows', () => {
|
|
267
|
+
// #given opencode-desktop binary detected, platform is Windows
|
|
268
|
+
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
269
|
+
process.env.APPDATA = 'C:\\Users\\TestUser\\AppData\\Roaming';
|
|
270
|
+
|
|
271
|
+
// #when getOpenCodeConfigDir is called with binary="opencode-desktop"
|
|
272
|
+
const result = getOpenCodeConfigDir({
|
|
273
|
+
binary: 'opencode-desktop',
|
|
274
|
+
version: '1.0.200',
|
|
275
|
+
checkExisting: false,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// #then returns %APPDATA%/ai.opencode.desktop
|
|
279
|
+
expect(result).toBe(
|
|
280
|
+
join('C:\\Users\\TestUser\\AppData\\Roaming', TAURI_APP_IDENTIFIER)
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('dev build detection', () => {
|
|
286
|
+
test('returns ai.opencode.desktop.dev path when dev version detected', () => {
|
|
287
|
+
// #given opencode-desktop dev version
|
|
288
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
289
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
290
|
+
|
|
291
|
+
// #when getOpenCodeConfigDir is called with dev version
|
|
292
|
+
const result = getOpenCodeConfigDir({
|
|
293
|
+
binary: 'opencode-desktop',
|
|
294
|
+
version: '1.0.0-dev.123',
|
|
295
|
+
checkExisting: false,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// #then returns path with ai.opencode.desktop.dev
|
|
299
|
+
expect(result).toBe(
|
|
300
|
+
join(homedir(), '.config', TAURI_APP_IDENTIFIER_DEV)
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('returns ai.opencode.desktop.dev on macOS for dev build', () => {
|
|
305
|
+
// #given opencode-desktop dev version on macOS
|
|
306
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
307
|
+
|
|
308
|
+
// #when getOpenCodeConfigDir is called with dev version
|
|
309
|
+
const result = getOpenCodeConfigDir({
|
|
310
|
+
binary: 'opencode-desktop',
|
|
311
|
+
version: '1.0.0-dev',
|
|
312
|
+
checkExisting: false,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// #then returns path with ai.opencode.desktop.dev
|
|
316
|
+
expect(result).toBe(
|
|
317
|
+
join(
|
|
318
|
+
homedir(),
|
|
319
|
+
'Library',
|
|
320
|
+
'Application Support',
|
|
321
|
+
TAURI_APP_IDENTIFIER_DEV
|
|
322
|
+
)
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('getOpenCodeConfigPaths', () => {
|
|
329
|
+
test('returns all config paths for CLI binary', () => {
|
|
330
|
+
// #given opencode CLI binary on Linux
|
|
331
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
332
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
333
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
334
|
+
|
|
335
|
+
// #when getOpenCodeConfigPaths is called
|
|
336
|
+
const paths = getOpenCodeConfigPaths({
|
|
337
|
+
binary: 'opencode',
|
|
338
|
+
version: '1.0.200',
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// #then returns all expected paths
|
|
342
|
+
const expectedDir = join(homedir(), '.config', 'opencode');
|
|
343
|
+
expect(paths.configDir).toBe(expectedDir);
|
|
344
|
+
expect(paths.configJson).toBe(join(expectedDir, 'opencode.json'));
|
|
345
|
+
expect(paths.configJsonc).toBe(join(expectedDir, 'opencode.jsonc'));
|
|
346
|
+
expect(paths.packageJson).toBe(join(expectedDir, 'package.json'));
|
|
347
|
+
expect(paths.omoConfig).toBe(join(expectedDir, 'oh-my-opencode.json'));
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('returns all config paths for desktop binary', () => {
|
|
351
|
+
// #given opencode-desktop binary on macOS
|
|
352
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
353
|
+
|
|
354
|
+
// #when getOpenCodeConfigPaths is called
|
|
355
|
+
const paths = getOpenCodeConfigPaths({
|
|
356
|
+
binary: 'opencode-desktop',
|
|
357
|
+
version: '1.0.200',
|
|
358
|
+
checkExisting: false,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// #then returns all expected paths
|
|
362
|
+
const expectedDir = join(
|
|
363
|
+
homedir(),
|
|
364
|
+
'Library',
|
|
365
|
+
'Application Support',
|
|
366
|
+
TAURI_APP_IDENTIFIER
|
|
367
|
+
);
|
|
368
|
+
expect(paths.configDir).toBe(expectedDir);
|
|
369
|
+
expect(paths.configJson).toBe(join(expectedDir, 'opencode.json'));
|
|
370
|
+
expect(paths.configJsonc).toBe(join(expectedDir, 'opencode.jsonc'));
|
|
371
|
+
expect(paths.packageJson).toBe(join(expectedDir, 'package.json'));
|
|
372
|
+
expect(paths.omoConfig).toBe(join(expectedDir, 'oh-my-opencode.json'));
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
describe('detectExistingConfigDir', () => {
|
|
377
|
+
test('returns null when no config exists', () => {
|
|
378
|
+
// #given no config files exist
|
|
379
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
380
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
381
|
+
delete process.env.OPENCODE_CONFIG_DIR;
|
|
382
|
+
|
|
383
|
+
// #when detectExistingConfigDir is called
|
|
384
|
+
const result = detectExistingConfigDir('opencode', '1.0.200');
|
|
385
|
+
|
|
386
|
+
// #then result is either null or a valid string path
|
|
387
|
+
expect(result === null || typeof result === 'string').toBe(true);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test('includes OPENCODE_CONFIG_DIR in search locations when set', () => {
|
|
391
|
+
// #given OPENCODE_CONFIG_DIR is set to a custom path
|
|
392
|
+
process.env.OPENCODE_CONFIG_DIR = '/custom/opencode/path';
|
|
393
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
394
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
395
|
+
|
|
396
|
+
// #when detectExistingConfigDir is called
|
|
397
|
+
const result = detectExistingConfigDir('opencode', '1.0.200');
|
|
398
|
+
|
|
399
|
+
// #then result is either null (no config file exists) or a valid string path
|
|
400
|
+
// The important thing is that the function doesn't throw
|
|
401
|
+
expect(result === null || typeof result === 'string').toBe(true);
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
});
|
|
@@ -2,12 +2,30 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
export type OpenCodeBinaryType = 'opencode' | 'opencode-desktop';
|
|
6
|
+
|
|
7
|
+
export interface OpenCodeConfigDirOptions {
|
|
8
|
+
binary: OpenCodeBinaryType;
|
|
7
9
|
version?: string | null;
|
|
8
10
|
checkExisting?: boolean;
|
|
9
11
|
}
|
|
10
12
|
|
|
13
|
+
export interface OpenCodeConfigPaths {
|
|
14
|
+
configDir: string;
|
|
15
|
+
configJson: string;
|
|
16
|
+
configJsonc: string;
|
|
17
|
+
packageJson: string;
|
|
18
|
+
omoConfig: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const TAURI_APP_IDENTIFIER = 'ai.opencode.desktop';
|
|
22
|
+
export const TAURI_APP_IDENTIFIER_DEV = 'ai.opencode.desktop.dev';
|
|
23
|
+
|
|
24
|
+
export function isDevBuild(version: string | null | undefined): boolean {
|
|
25
|
+
if (!version) return false;
|
|
26
|
+
return version.includes('-dev') || version.includes('.dev');
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
function getTauriConfigDir(identifier: string): string {
|
|
12
30
|
const platform = process.platform;
|
|
13
31
|
|
|
@@ -63,15 +81,76 @@ function getCliConfigDir(): string {
|
|
|
63
81
|
export function getOpenCodeConfigDir(
|
|
64
82
|
options: OpenCodeConfigDirOptions
|
|
65
83
|
): string {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
const { binary, version, checkExisting = true } = options;
|
|
85
|
+
|
|
86
|
+
if (binary === 'opencode') {
|
|
87
|
+
return getCliConfigDir();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const identifier = isDevBuild(version)
|
|
91
|
+
? TAURI_APP_IDENTIFIER_DEV
|
|
92
|
+
: TAURI_APP_IDENTIFIER;
|
|
93
|
+
const tauriDir = getTauriConfigDir(identifier);
|
|
94
|
+
|
|
95
|
+
if (checkExisting) {
|
|
96
|
+
const legacyDir = getCliConfigDir();
|
|
97
|
+
const legacyConfig = join(legacyDir, 'opencode.json');
|
|
98
|
+
const legacyConfigC = join(legacyDir, 'opencode.jsonc');
|
|
99
|
+
|
|
100
|
+
if (existsSync(legacyConfig) || existsSync(legacyConfigC)) {
|
|
101
|
+
return legacyDir;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return tauriDir;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function getOpenCodeConfigPaths(
|
|
109
|
+
options: OpenCodeConfigDirOptions
|
|
110
|
+
): OpenCodeConfigPaths {
|
|
111
|
+
const configDir = getOpenCodeConfigDir(options);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
configDir,
|
|
115
|
+
configJson: join(configDir, 'opencode.json'),
|
|
116
|
+
configJsonc: join(configDir, 'opencode.jsonc'),
|
|
117
|
+
packageJson: join(configDir, 'package.json'),
|
|
118
|
+
omoConfig: join(configDir, 'oh-my-opencode.json'),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function detectExistingConfigDir(
|
|
123
|
+
binary: OpenCodeBinaryType,
|
|
124
|
+
version?: string | null
|
|
125
|
+
): string | null {
|
|
126
|
+
const locations: string[] = [];
|
|
127
|
+
|
|
128
|
+
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
129
|
+
if (envConfigDir) {
|
|
130
|
+
locations.push(resolve(envConfigDir));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (binary === 'opencode-desktop') {
|
|
134
|
+
const identifier = isDevBuild(version)
|
|
135
|
+
? TAURI_APP_IDENTIFIER_DEV
|
|
136
|
+
: TAURI_APP_IDENTIFIER;
|
|
137
|
+
locations.push(getTauriConfigDir(identifier));
|
|
138
|
+
|
|
139
|
+
if (isDevBuild(version)) {
|
|
140
|
+
locations.push(getTauriConfigDir(TAURI_APP_IDENTIFIER));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
locations.push(getCliConfigDir());
|
|
145
|
+
|
|
146
|
+
for (const dir of locations) {
|
|
147
|
+
const configJson = join(dir, 'opencode.json');
|
|
148
|
+
const configJsonc = join(dir, 'opencode.jsonc');
|
|
149
|
+
|
|
150
|
+
if (existsSync(configJson) || existsSync(configJsonc)) {
|
|
151
|
+
return dir;
|
|
152
|
+
}
|
|
74
153
|
}
|
|
75
154
|
|
|
76
|
-
return
|
|
155
|
+
return null;
|
|
77
156
|
}
|