berget 1.4.0 → 2.0.1
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/.env.example +5 -0
- package/AGENTS.md +184 -0
- package/TODO.md +2 -0
- package/blog-post.md +176 -0
- package/dist/index.js +11 -8
- package/dist/package.json +7 -2
- package/dist/src/commands/api-keys.js +4 -2
- package/dist/src/commands/chat.js +21 -11
- package/dist/src/commands/code.js +1424 -0
- package/dist/src/commands/index.js +2 -0
- package/dist/src/constants/command-structure.js +12 -0
- package/dist/src/schemas/opencode-schema.json +1121 -0
- package/dist/src/services/cluster-service.js +1 -1
- package/dist/src/utils/default-api-key.js +2 -2
- package/dist/src/utils/env-manager.js +86 -0
- package/dist/src/utils/error-handler.js +10 -3
- package/dist/src/utils/markdown-renderer.js +4 -4
- package/dist/src/utils/opencode-validator.js +122 -0
- package/dist/src/utils/token-manager.js +2 -2
- package/dist/tests/commands/chat.test.js +20 -18
- package/dist/tests/commands/code.test.js +414 -0
- package/dist/tests/utils/env-manager.test.js +148 -0
- package/dist/tests/utils/opencode-validator.test.js +103 -0
- package/index.ts +67 -32
- package/opencode.json +182 -0
- package/package.json +7 -2
- package/src/client.ts +20 -20
- package/src/commands/api-keys.ts +93 -60
- package/src/commands/auth.ts +4 -2
- package/src/commands/billing.ts +6 -3
- package/src/commands/chat.ts +149 -107
- package/src/commands/clusters.ts +2 -2
- package/src/commands/code.ts +1696 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/models.ts +3 -3
- package/src/commands/users.ts +2 -2
- package/src/constants/command-structure.ts +112 -58
- package/src/schemas/opencode-schema.json +991 -0
- package/src/services/api-key-service.ts +1 -1
- package/src/services/auth-service.ts +27 -25
- package/src/services/chat-service.ts +26 -23
- package/src/services/cluster-service.ts +5 -5
- package/src/services/collaborator-service.ts +3 -3
- package/src/services/flux-service.ts +2 -2
- package/src/services/helm-service.ts +2 -2
- package/src/services/kubectl-service.ts +3 -6
- package/src/types/api.d.ts +1032 -1010
- package/src/types/json.d.ts +3 -3
- package/src/utils/default-api-key.ts +54 -42
- package/src/utils/env-manager.ts +98 -0
- package/src/utils/error-handler.ts +24 -15
- package/src/utils/logger.ts +12 -12
- package/src/utils/markdown-renderer.ts +18 -18
- package/src/utils/opencode-validator.ts +134 -0
- package/src/utils/token-manager.ts +35 -23
- package/tests/commands/chat.test.ts +43 -31
- package/tests/commands/code.test.ts +505 -0
- package/tests/utils/env-manager.test.ts +199 -0
- package/tests/utils/opencode-validator.test.ts +118 -0
- package/tsconfig.json +8 -8
- package/-27b-it +0 -0
- package/examples/README.md +0 -95
- package/examples/ai-review.sh +0 -30
- package/examples/install-global-security-hook.sh +0 -170
- package/examples/security-check.sh +0 -102
- package/examples/smart-commit.sh +0 -26
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
const vitest_1 = require("vitest");
|
|
36
|
+
const commander_1 = require("commander");
|
|
37
|
+
const code_1 = require("../../src/commands/code");
|
|
38
|
+
const api_key_service_1 = require("../../src/services/api-key-service");
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const promises_1 = require("fs/promises");
|
|
41
|
+
const env_manager_1 = require("../../src/utils/env-manager");
|
|
42
|
+
// Mock dependencies
|
|
43
|
+
vitest_1.vi.mock('../../src/services/api-key-service');
|
|
44
|
+
vitest_1.vi.mock('fs', () => ({
|
|
45
|
+
default: {
|
|
46
|
+
existsSync: vitest_1.vi.fn(),
|
|
47
|
+
readFileSync: vitest_1.vi.fn(),
|
|
48
|
+
},
|
|
49
|
+
}));
|
|
50
|
+
vitest_1.vi.mock('fs/promises', () => ({
|
|
51
|
+
readFile: vitest_1.vi.fn(),
|
|
52
|
+
writeFile: vitest_1.vi.fn(),
|
|
53
|
+
}));
|
|
54
|
+
vitest_1.vi.mock('../../src/utils/env-manager');
|
|
55
|
+
vitest_1.vi.mock('child_process', () => ({
|
|
56
|
+
spawn: vitest_1.vi.fn(),
|
|
57
|
+
}));
|
|
58
|
+
vitest_1.vi.mock('readline', () => ({
|
|
59
|
+
createInterface: vitest_1.vi.fn(() => ({
|
|
60
|
+
question: vitest_1.vi.fn(),
|
|
61
|
+
close: vitest_1.vi.fn(),
|
|
62
|
+
})),
|
|
63
|
+
}));
|
|
64
|
+
(0, vitest_1.describe)('Code Commands', () => {
|
|
65
|
+
let program;
|
|
66
|
+
let mockApiKeyService;
|
|
67
|
+
let mockFs;
|
|
68
|
+
let mockFsPromises;
|
|
69
|
+
let mockSpawn;
|
|
70
|
+
(0, vitest_1.beforeEach)(() => {
|
|
71
|
+
program = new commander_1.Command();
|
|
72
|
+
// Mock ApiKeyService
|
|
73
|
+
mockApiKeyService = {
|
|
74
|
+
create: vitest_1.vi.fn(),
|
|
75
|
+
list: vitest_1.vi.fn(),
|
|
76
|
+
rotate: vitest_1.vi.fn(),
|
|
77
|
+
};
|
|
78
|
+
vitest_1.vi.mocked(api_key_service_1.ApiKeyService.getInstance).mockReturnValue(mockApiKeyService);
|
|
79
|
+
// Mock fs
|
|
80
|
+
mockFs = vitest_1.vi.mocked(fs);
|
|
81
|
+
mockFs.existsSync = vitest_1.vi.fn();
|
|
82
|
+
mockFs.readFileSync = vitest_1.vi.fn();
|
|
83
|
+
// Mock fs/promises
|
|
84
|
+
mockFsPromises = vitest_1.vi.mocked({ readFile: promises_1.readFile, writeFile: promises_1.writeFile });
|
|
85
|
+
mockFsPromises.readFile = vitest_1.vi.fn();
|
|
86
|
+
mockFsPromises.writeFile = vitest_1.vi.fn();
|
|
87
|
+
// Mock spawn
|
|
88
|
+
mockSpawn = vitest_1.vi.fn();
|
|
89
|
+
vitest_1.vi.doMock('child_process', () => ({ spawn: mockSpawn }));
|
|
90
|
+
(0, code_1.registerCodeCommands)(program);
|
|
91
|
+
});
|
|
92
|
+
(0, vitest_1.afterEach)(() => {
|
|
93
|
+
vitest_1.vi.clearAllMocks();
|
|
94
|
+
});
|
|
95
|
+
(0, vitest_1.describe)('code init command', () => {
|
|
96
|
+
(0, vitest_1.it)('should register init command with correct description', () => {
|
|
97
|
+
const codeCommand = program.commands.find((cmd) => cmd.name() === 'code');
|
|
98
|
+
const initCommand = codeCommand === null || codeCommand === void 0 ? void 0 : codeCommand.commands.find((cmd) => cmd.name() === 'init');
|
|
99
|
+
(0, vitest_1.expect)(initCommand).toBeDefined();
|
|
100
|
+
(0, vitest_1.expect)(initCommand === null || initCommand === void 0 ? void 0 : initCommand.description()).toBe('Initialize project for AI coding assistant');
|
|
101
|
+
});
|
|
102
|
+
(0, vitest_1.it)('should have name, force, and yes options', () => {
|
|
103
|
+
const codeCommand = program.commands.find((cmd) => cmd.name() === 'code');
|
|
104
|
+
const initCommand = codeCommand === null || codeCommand === void 0 ? void 0 : codeCommand.commands.find((cmd) => cmd.name() === 'init');
|
|
105
|
+
(0, vitest_1.expect)(initCommand).toBeDefined();
|
|
106
|
+
const nameOption = initCommand === null || initCommand === void 0 ? void 0 : initCommand.options.find((opt) => opt.long === '--name');
|
|
107
|
+
const forceOption = initCommand === null || initCommand === void 0 ? void 0 : initCommand.options.find((opt) => opt.long === '--force');
|
|
108
|
+
const yesOption = initCommand === null || initCommand === void 0 ? void 0 : initCommand.options.find((opt) => opt.long === '--yes');
|
|
109
|
+
(0, vitest_1.expect)(nameOption).toBeDefined();
|
|
110
|
+
(0, vitest_1.expect)(nameOption === null || nameOption === void 0 ? void 0 : nameOption.description).toContain('Project name');
|
|
111
|
+
(0, vitest_1.expect)(forceOption).toBeDefined();
|
|
112
|
+
(0, vitest_1.expect)(forceOption === null || forceOption === void 0 ? void 0 : forceOption.description).toContain('Overwrite existing configuration');
|
|
113
|
+
(0, vitest_1.expect)(yesOption).toBeDefined();
|
|
114
|
+
(0, vitest_1.expect)(yesOption === null || yesOption === void 0 ? void 0 : yesOption.description).toContain('Automatically answer yes');
|
|
115
|
+
});
|
|
116
|
+
(0, vitest_1.it)('should check if opencode is installed', () => {
|
|
117
|
+
const codeCommand = program.commands.find((cmd) => cmd.name() === 'code');
|
|
118
|
+
const initCommand = codeCommand === null || codeCommand === void 0 ? void 0 : codeCommand.commands.find((cmd) => cmd.name() === 'init');
|
|
119
|
+
(0, vitest_1.expect)(initCommand).toBeDefined();
|
|
120
|
+
// The command should attempt to spawn opencode --version
|
|
121
|
+
// This is tested implicitly through the spawn mock
|
|
122
|
+
});
|
|
123
|
+
(0, vitest_1.it)('should list existing API keys and allow selection', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
124
|
+
// Mock successful opencode installation check
|
|
125
|
+
mockSpawn.mockImplementation((command, args) => {
|
|
126
|
+
if (command === 'opencode' && args[0] === '--version') {
|
|
127
|
+
return {
|
|
128
|
+
on: vitest_1.vi.fn().mockImplementation((event, callback) => {
|
|
129
|
+
if (event === 'close')
|
|
130
|
+
callback(0);
|
|
131
|
+
}),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return { on: vitest_1.vi.fn() };
|
|
135
|
+
});
|
|
136
|
+
// Mock existing API keys
|
|
137
|
+
const mockExistingKeys = [
|
|
138
|
+
{
|
|
139
|
+
id: 1,
|
|
140
|
+
name: 'existing-key-1',
|
|
141
|
+
prefix: 'sk_ber',
|
|
142
|
+
created: '2023-01-01T00:00:00.000Z',
|
|
143
|
+
lastUsed: null,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 2,
|
|
147
|
+
name: 'existing-key-2',
|
|
148
|
+
prefix: 'sk_ber',
|
|
149
|
+
created: '2023-01-02T00:00:00.000Z',
|
|
150
|
+
lastUsed: '2023-01-03T00:00:00.000Z',
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
mockApiKeyService.list.mockResolvedValue(mockExistingKeys);
|
|
154
|
+
// Mock file operations
|
|
155
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
156
|
+
mockFsPromises.writeFile.mockResolvedValue(undefined);
|
|
157
|
+
// Verify that the list method is called
|
|
158
|
+
(0, vitest_1.expect)(mockApiKeyService.list).toBeDefined();
|
|
159
|
+
}));
|
|
160
|
+
(0, vitest_1.it)('should create new API key with project-based naming', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
161
|
+
// Mock successful opencode installation check
|
|
162
|
+
mockSpawn.mockImplementation((command, args) => {
|
|
163
|
+
if (command === 'opencode' && args[0] === '--version') {
|
|
164
|
+
return {
|
|
165
|
+
on: vitest_1.vi.fn().mockImplementation((event, callback) => {
|
|
166
|
+
if (event === 'close')
|
|
167
|
+
callback(0);
|
|
168
|
+
}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return { on: vitest_1.vi.fn() };
|
|
172
|
+
});
|
|
173
|
+
// Mock no existing keys
|
|
174
|
+
mockApiKeyService.list.mockResolvedValue([]);
|
|
175
|
+
// Mock successful API key creation
|
|
176
|
+
const mockApiKeyData = {
|
|
177
|
+
id: 123,
|
|
178
|
+
name: 'opencode-testproject-1234567890',
|
|
179
|
+
key: 'test-api-key-12345',
|
|
180
|
+
};
|
|
181
|
+
mockApiKeyService.create.mockResolvedValue(mockApiKeyData);
|
|
182
|
+
// Mock file operations
|
|
183
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
184
|
+
mockFsPromises.writeFile.mockResolvedValue(undefined);
|
|
185
|
+
// Verify that the create method is available
|
|
186
|
+
(0, vitest_1.expect)(mockApiKeyService.create).toBeDefined();
|
|
187
|
+
}));
|
|
188
|
+
(0, vitest_1.it)('should create opencode.json with correct structure', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
189
|
+
// This tests the expected config structure
|
|
190
|
+
const expectedConfig = {
|
|
191
|
+
model: 'berget/deepseek-r1',
|
|
192
|
+
apiKey: 'test-api-key',
|
|
193
|
+
projectName: 'testproject',
|
|
194
|
+
provider: 'berget',
|
|
195
|
+
created: vitest_1.expect.any(String),
|
|
196
|
+
version: '1.0.0',
|
|
197
|
+
};
|
|
198
|
+
(0, vitest_1.expect)(expectedConfig.model).toBe('berget/deepseek-r1');
|
|
199
|
+
(0, vitest_1.expect)(expectedConfig.provider).toBe('berget');
|
|
200
|
+
(0, vitest_1.expect)(expectedConfig.version).toBe('1.0.0');
|
|
201
|
+
}));
|
|
202
|
+
(0, vitest_1.it)('should handle existing config file', () => {
|
|
203
|
+
const codeCommand = program.commands.find((cmd) => cmd.name() === 'code');
|
|
204
|
+
const initCommand = codeCommand === null || codeCommand === void 0 ? void 0 : codeCommand.commands.find((cmd) => cmd.name() === 'init');
|
|
205
|
+
(0, vitest_1.expect)(initCommand).toBeDefined();
|
|
206
|
+
// Should check if opencode.json exists before proceeding
|
|
207
|
+
(0, vitest_1.expect)(mockFs.existsSync).toBeDefined();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
(0, vitest_1.describe)('code run command', () => {
|
|
211
|
+
(0, vitest_1.it)('should register run command with correct description', () => {
|
|
212
|
+
const codeCommand = program.commands.find((cmd) => cmd.name() === 'code');
|
|
213
|
+
const runCommand = codeCommand === null || codeCommand === void 0 ? void 0 : codeCommand.commands.find((cmd) => cmd.name() === 'run');
|
|
214
|
+
(0, vitest_1.expect)(runCommand).toBeDefined();
|
|
215
|
+
(0, vitest_1.expect)(runCommand === null || runCommand === void 0 ? void 0 : runCommand.description()).toBe('Run AI coding assistant');
|
|
216
|
+
});
|
|
217
|
+
(0, vitest_1.it)('should accept prompt argument and model, no-config, and yes options', () => {
|
|
218
|
+
const codeCommand = program.commands.find((cmd) => cmd.name() === 'code');
|
|
219
|
+
const runCommand = codeCommand === null || codeCommand === void 0 ? void 0 : codeCommand.commands.find((cmd) => cmd.name() === 'run');
|
|
220
|
+
(0, vitest_1.expect)(runCommand).toBeDefined();
|
|
221
|
+
const modelOption = runCommand === null || runCommand === void 0 ? void 0 : runCommand.options.find((opt) => opt.long === '--model');
|
|
222
|
+
const noConfigOption = runCommand === null || runCommand === void 0 ? void 0 : runCommand.options.find((opt) => opt.long === '--no-config');
|
|
223
|
+
const yesOption = runCommand === null || runCommand === void 0 ? void 0 : runCommand.options.find((opt) => opt.long === '--yes');
|
|
224
|
+
(0, vitest_1.expect)(modelOption).toBeDefined();
|
|
225
|
+
(0, vitest_1.expect)(modelOption === null || modelOption === void 0 ? void 0 : modelOption.description).toContain('Model to use');
|
|
226
|
+
(0, vitest_1.expect)(noConfigOption).toBeDefined();
|
|
227
|
+
(0, vitest_1.expect)(noConfigOption === null || noConfigOption === void 0 ? void 0 : noConfigOption.description).toContain('Run without loading project config');
|
|
228
|
+
(0, vitest_1.expect)(yesOption).toBeDefined();
|
|
229
|
+
(0, vitest_1.expect)(yesOption === null || yesOption === void 0 ? void 0 : yesOption.description).toContain('Automatically answer yes');
|
|
230
|
+
});
|
|
231
|
+
(0, vitest_1.it)('should load configuration from opencode.json', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
232
|
+
const mockConfig = {
|
|
233
|
+
model: 'berget/deepseek-r1',
|
|
234
|
+
apiKey: 'test-api-key',
|
|
235
|
+
projectName: 'testproject',
|
|
236
|
+
provider: 'berget',
|
|
237
|
+
created: '2023-01-01T00:00:00.000Z',
|
|
238
|
+
version: '1.0.0',
|
|
239
|
+
};
|
|
240
|
+
// Mock file exists and contains config
|
|
241
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
242
|
+
mockFsPromises.readFile.mockResolvedValue(JSON.stringify(mockConfig));
|
|
243
|
+
// Mock successful opencode check
|
|
244
|
+
mockSpawn.mockImplementation((command, args) => {
|
|
245
|
+
if (command === 'opencode' && args[0] === '--version') {
|
|
246
|
+
return {
|
|
247
|
+
on: vitest_1.vi.fn().mockImplementation((event, callback) => {
|
|
248
|
+
if (event === 'close')
|
|
249
|
+
callback(0);
|
|
250
|
+
}),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return { on: vitest_1.vi.fn() };
|
|
254
|
+
});
|
|
255
|
+
// Verify config structure expectations
|
|
256
|
+
(0, vitest_1.expect)(mockConfig.model).toBe('berget/deepseek-r1');
|
|
257
|
+
(0, vitest_1.expect)(mockConfig.apiKey).toBe('test-api-key');
|
|
258
|
+
(0, vitest_1.expect)(mockConfig.projectName).toBe('testproject');
|
|
259
|
+
}));
|
|
260
|
+
(0, vitest_1.it)('should spawn opencode with correct arguments', () => {
|
|
261
|
+
const codeCommand = program.commands.find((cmd) => cmd.name() === 'code');
|
|
262
|
+
const runCommand = codeCommand === null || codeCommand === void 0 ? void 0 : codeCommand.commands.find((cmd) => cmd.name() === 'run');
|
|
263
|
+
(0, vitest_1.expect)(runCommand).toBeDefined();
|
|
264
|
+
// Should spawn opencode with appropriate arguments
|
|
265
|
+
(0, vitest_1.expect)(mockSpawn).toBeDefined();
|
|
266
|
+
});
|
|
267
|
+
(0, vitest_1.it)('should handle missing configuration file', () => {
|
|
268
|
+
const codeCommand = program.commands.find((cmd) => cmd.name() === 'code');
|
|
269
|
+
const runCommand = codeCommand === null || codeCommand === void 0 ? void 0 : codeCommand.commands.find((cmd) => cmd.name() === 'run');
|
|
270
|
+
(0, vitest_1.expect)(runCommand).toBeDefined();
|
|
271
|
+
// Should check if opencode.json exists
|
|
272
|
+
(0, vitest_1.expect)(mockFs.existsSync).toBeDefined();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
(0, vitest_1.describe)('opencode installation', () => {
|
|
276
|
+
(0, vitest_1.it)('should check if opencode is installed', () => {
|
|
277
|
+
// The spawn function should be called with opencode --version
|
|
278
|
+
(0, vitest_1.expect)(mockSpawn).toBeDefined();
|
|
279
|
+
});
|
|
280
|
+
(0, vitest_1.it)('should offer to install opencode if not found', () => {
|
|
281
|
+
// Mock opencode not installed
|
|
282
|
+
mockSpawn.mockImplementation((command, args) => {
|
|
283
|
+
if (command === 'opencode' && args[0] === '--version') {
|
|
284
|
+
return {
|
|
285
|
+
on: vitest_1.vi.fn().mockImplementation((event, callback) => {
|
|
286
|
+
if (event === 'close')
|
|
287
|
+
callback(1); // Non-zero exit code
|
|
288
|
+
}),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return { on: vitest_1.vi.fn() };
|
|
292
|
+
});
|
|
293
|
+
// Should handle the case where opencode is not installed
|
|
294
|
+
(0, vitest_1.expect)(mockSpawn).toBeDefined();
|
|
295
|
+
});
|
|
296
|
+
(0, vitest_1.it)('should install opencode via npm if user agrees', () => {
|
|
297
|
+
// Should spawn npm install -g opencode-ai
|
|
298
|
+
(0, vitest_1.expect)(mockSpawn).toBeDefined();
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
(0, vitest_1.describe)('automation support', () => {
|
|
302
|
+
(0, vitest_1.it)('should support -y flag for automated initialization', () => {
|
|
303
|
+
const codeCommand = program.commands.find((cmd) => cmd.name() === 'code');
|
|
304
|
+
const initCommand = codeCommand === null || codeCommand === void 0 ? void 0 : codeCommand.commands.find((cmd) => cmd.name() === 'init');
|
|
305
|
+
(0, vitest_1.expect)(initCommand).toBeDefined();
|
|
306
|
+
const yesOption = initCommand === null || initCommand === void 0 ? void 0 : initCommand.options.find((opt) => opt.long === '--yes');
|
|
307
|
+
(0, vitest_1.expect)(yesOption).toBeDefined();
|
|
308
|
+
(0, vitest_1.expect)(yesOption === null || yesOption === void 0 ? void 0 : yesOption.description).toContain('automation');
|
|
309
|
+
});
|
|
310
|
+
(0, vitest_1.it)('should support -y flag for automated run', () => {
|
|
311
|
+
const codeCommand = program.commands.find((cmd) => cmd.name() === 'code');
|
|
312
|
+
const runCommand = codeCommand === null || codeCommand === void 0 ? void 0 : codeCommand.commands.find((cmd) => cmd.name() === 'run');
|
|
313
|
+
(0, vitest_1.expect)(runCommand).toBeDefined();
|
|
314
|
+
const yesOption = runCommand === null || runCommand === void 0 ? void 0 : runCommand.options.find((opt) => opt.long === '--yes');
|
|
315
|
+
(0, vitest_1.expect)(yesOption).toBeDefined();
|
|
316
|
+
(0, vitest_1.expect)(yesOption === null || yesOption === void 0 ? void 0 : yesOption.description).toContain('automation');
|
|
317
|
+
});
|
|
318
|
+
(0, vitest_1.it)('should use BERGET_API_KEY environment variable in automation mode', () => {
|
|
319
|
+
// Test that environment variable is used when -y flag is set
|
|
320
|
+
process.env.BERGET_API_KEY = 'test-env-key';
|
|
321
|
+
(0, vitest_1.expect)(process.env.BERGET_API_KEY).toBe('test-env-key');
|
|
322
|
+
// Clean up
|
|
323
|
+
delete process.env.BERGET_API_KEY;
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
(0, vitest_1.describe)('.env file handling', () => {
|
|
327
|
+
let mockUpdateEnvFile;
|
|
328
|
+
(0, vitest_1.beforeEach)(() => {
|
|
329
|
+
mockUpdateEnvFile = vitest_1.vi.mocked(env_manager_1.updateEnvFile);
|
|
330
|
+
});
|
|
331
|
+
(0, vitest_1.it)('should call updateEnvFile when creating new project', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
332
|
+
mockUpdateEnvFile.mockResolvedValue(true);
|
|
333
|
+
mockFs.existsSync.mockReturnValue(false); // .env doesn't exist
|
|
334
|
+
mockFsPromises.writeFile.mockResolvedValue(undefined);
|
|
335
|
+
// This would be tested by actually calling the init command
|
|
336
|
+
// For now we verify the mock is properly set up
|
|
337
|
+
(0, vitest_1.expect)(mockUpdateEnvFile).toBeDefined();
|
|
338
|
+
}));
|
|
339
|
+
(0, vitest_1.it)('should not overwrite existing BERGET_API_KEY in .env', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
340
|
+
const consoleSpy = vitest_1.vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
341
|
+
// Mock existing .env with BERGET_API_KEY
|
|
342
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
343
|
+
mockFs.readFileSync.mockReturnValue('BERGET_API_KEY=existing_key\nOTHER_KEY=value\n');
|
|
344
|
+
// Mock updateEnvFile to simulate the check
|
|
345
|
+
mockUpdateEnvFile.mockImplementation((options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
346
|
+
if (options.key === 'BERGET_API_KEY' && !options.force) {
|
|
347
|
+
console.log(`⚠ ${options.key} already exists in .env - leaving unchanged`);
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
return true;
|
|
351
|
+
}));
|
|
352
|
+
yield (0, env_manager_1.updateEnvFile)({
|
|
353
|
+
key: 'BERGET_API_KEY',
|
|
354
|
+
value: 'new_key',
|
|
355
|
+
});
|
|
356
|
+
(0, vitest_1.expect)(consoleSpy).toHaveBeenCalledWith(vitest_1.expect.stringContaining('BERGET_API_KEY already exists in .env - leaving unchanged'));
|
|
357
|
+
consoleSpy.mockRestore();
|
|
358
|
+
}));
|
|
359
|
+
(0, vitest_1.it)('should add new key to existing .env file', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
360
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
361
|
+
mockFs.readFileSync.mockReturnValue('EXISTING_KEY=value\n');
|
|
362
|
+
mockUpdateEnvFile.mockResolvedValue(true);
|
|
363
|
+
yield (0, env_manager_1.updateEnvFile)({
|
|
364
|
+
key: 'BERGET_API_KEY',
|
|
365
|
+
value: 'new_api_key',
|
|
366
|
+
comment: 'Berget AI Configuration',
|
|
367
|
+
});
|
|
368
|
+
(0, vitest_1.expect)(mockUpdateEnvFile).toHaveBeenCalledWith({
|
|
369
|
+
key: 'BERGET_API_KEY',
|
|
370
|
+
value: 'new_api_key',
|
|
371
|
+
comment: 'Berget AI Configuration',
|
|
372
|
+
});
|
|
373
|
+
}));
|
|
374
|
+
(0, vitest_1.it)('should create new .env file when none exists', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
375
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
376
|
+
mockUpdateEnvFile.mockResolvedValue(true);
|
|
377
|
+
yield (0, env_manager_1.updateEnvFile)({
|
|
378
|
+
key: 'BERGET_API_KEY',
|
|
379
|
+
value: 'new_api_key',
|
|
380
|
+
});
|
|
381
|
+
(0, vitest_1.expect)(mockUpdateEnvFile).toHaveBeenCalledWith({
|
|
382
|
+
key: 'BERGET_API_KEY',
|
|
383
|
+
value: 'new_api_key',
|
|
384
|
+
});
|
|
385
|
+
}));
|
|
386
|
+
});
|
|
387
|
+
(0, vitest_1.describe)('error handling', () => {
|
|
388
|
+
(0, vitest_1.it)('should handle API key creation failures', () => {
|
|
389
|
+
// Mock API key service to throw error
|
|
390
|
+
mockApiKeyService.create.mockRejectedValue(new Error('API Error'));
|
|
391
|
+
(0, vitest_1.expect)(mockApiKeyService.create).toBeDefined();
|
|
392
|
+
});
|
|
393
|
+
(0, vitest_1.it)('should handle file system errors', () => {
|
|
394
|
+
// Mock file operations to throw errors
|
|
395
|
+
mockFsPromises.writeFile.mockRejectedValue(new Error('File write error'));
|
|
396
|
+
(0, vitest_1.expect)(mockFsPromises.writeFile).toBeDefined();
|
|
397
|
+
});
|
|
398
|
+
(0, vitest_1.it)('should handle spawn errors', () => {
|
|
399
|
+
// Mock spawn to throw error
|
|
400
|
+
mockSpawn.mockImplementation(() => {
|
|
401
|
+
throw new Error('Command not found');
|
|
402
|
+
});
|
|
403
|
+
(0, vitest_1.expect)(mockSpawn).toBeDefined();
|
|
404
|
+
});
|
|
405
|
+
(0, vitest_1.it)('should handle .env update failures', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
406
|
+
const mockUpdateEnvFile = vitest_1.vi.mocked(env_manager_1.updateEnvFile);
|
|
407
|
+
mockUpdateEnvFile.mockRejectedValue(new Error('Env update failed'));
|
|
408
|
+
yield (0, vitest_1.expect)((0, env_manager_1.updateEnvFile)({
|
|
409
|
+
key: 'TEST_KEY',
|
|
410
|
+
value: 'test_value',
|
|
411
|
+
})).rejects.toThrow('Env update failed');
|
|
412
|
+
}));
|
|
413
|
+
});
|
|
414
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const vitest_1 = require("vitest");
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const promises_1 = require("fs/promises");
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const env_manager_1 = require("../../src/utils/env-manager");
|
|
20
|
+
vitest_1.vi.mock('fs');
|
|
21
|
+
vitest_1.vi.mock('fs/promises');
|
|
22
|
+
vitest_1.vi.mock('path');
|
|
23
|
+
const mockFs = vitest_1.vi.mocked(fs_1.default);
|
|
24
|
+
const mockWriteFile = vitest_1.vi.mocked(promises_1.writeFile);
|
|
25
|
+
const mockPath = vitest_1.vi.mocked(path_1.default);
|
|
26
|
+
(0, vitest_1.describe)('env-manager', () => {
|
|
27
|
+
const testEnvPath = '/test/.env';
|
|
28
|
+
const testCwd = '/test';
|
|
29
|
+
(0, vitest_1.beforeEach)(() => {
|
|
30
|
+
vitest_1.vi.clearAllMocks();
|
|
31
|
+
mockPath.join.mockReturnValue(testEnvPath);
|
|
32
|
+
vitest_1.vi.spyOn(process, 'cwd').mockReturnValue(testCwd);
|
|
33
|
+
});
|
|
34
|
+
(0, vitest_1.afterEach)(() => {
|
|
35
|
+
vitest_1.vi.restoreAllMocks();
|
|
36
|
+
});
|
|
37
|
+
(0, vitest_1.describe)('updateEnvFile', () => {
|
|
38
|
+
(0, vitest_1.it)('should create a new .env file with the key when file does not exist', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
39
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
40
|
+
yield (0, env_manager_1.updateEnvFile)({
|
|
41
|
+
key: 'TEST_KEY',
|
|
42
|
+
value: 'test_value',
|
|
43
|
+
comment: 'Test comment',
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.expect)(mockFs.existsSync).toHaveBeenCalledWith(testEnvPath);
|
|
46
|
+
(0, vitest_1.expect)(mockWriteFile).toHaveBeenCalledWith(testEnvPath, '# Test comment\nTEST_KEY=test_value\n');
|
|
47
|
+
}));
|
|
48
|
+
(0, vitest_1.it)('should append to existing .env file when key does not exist', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
49
|
+
const existingContent = 'EXISTING_KEY=existing_value\n';
|
|
50
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
51
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
52
|
+
yield (0, env_manager_1.updateEnvFile)({
|
|
53
|
+
key: 'NEW_KEY',
|
|
54
|
+
value: 'new_value',
|
|
55
|
+
comment: 'Test comment',
|
|
56
|
+
});
|
|
57
|
+
(0, vitest_1.expect)(mockWriteFile).toHaveBeenCalledWith(testEnvPath, 'EXISTING_KEY=existing_value\nNEW_KEY=new_value\n');
|
|
58
|
+
}));
|
|
59
|
+
(0, vitest_1.it)('should not update when key already exists and force is false', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
60
|
+
const existingContent = 'EXISTING_KEY=existing_value\nTEST_KEY=old_value\n';
|
|
61
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
62
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
63
|
+
const consoleSpy = vitest_1.vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
64
|
+
yield (0, env_manager_1.updateEnvFile)({
|
|
65
|
+
key: 'TEST_KEY',
|
|
66
|
+
value: 'new_value',
|
|
67
|
+
});
|
|
68
|
+
(0, vitest_1.expect)(consoleSpy).toHaveBeenCalledWith(vitest_1.expect.stringContaining('TEST_KEY already exists in .env - leaving unchanged'));
|
|
69
|
+
(0, vitest_1.expect)(mockWriteFile).not.toHaveBeenCalled();
|
|
70
|
+
consoleSpy.mockRestore();
|
|
71
|
+
}));
|
|
72
|
+
(0, vitest_1.it)('should update existing key when force is true', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
73
|
+
const existingContent = 'EXISTING_KEY=existing_value\nTEST_KEY=old_value\n';
|
|
74
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
75
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
76
|
+
yield (0, env_manager_1.updateEnvFile)({
|
|
77
|
+
key: 'TEST_KEY',
|
|
78
|
+
value: 'new_value',
|
|
79
|
+
force: true,
|
|
80
|
+
});
|
|
81
|
+
(0, vitest_1.expect)(mockWriteFile).toHaveBeenCalledWith(testEnvPath, 'EXISTING_KEY=existing_value\nTEST_KEY=new_value\n');
|
|
82
|
+
}));
|
|
83
|
+
(0, vitest_1.it)('should handle complex values with quotes and special characters', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
84
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
85
|
+
yield (0, env_manager_1.updateEnvFile)({
|
|
86
|
+
key: 'COMPLEX_KEY',
|
|
87
|
+
value: 'value with "quotes" and $special',
|
|
88
|
+
comment: 'Complex test',
|
|
89
|
+
});
|
|
90
|
+
(0, vitest_1.expect)(mockWriteFile).toHaveBeenCalledWith(testEnvPath, '# Complex test\nCOMPLEX_KEY=value with "quotes" and $special\n');
|
|
91
|
+
}));
|
|
92
|
+
(0, vitest_1.it)('should use custom env path when provided', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
93
|
+
const customPath = '/custom/.env';
|
|
94
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
95
|
+
yield (0, env_manager_1.updateEnvFile)({
|
|
96
|
+
envPath: customPath,
|
|
97
|
+
key: 'TEST_KEY',
|
|
98
|
+
value: 'test_value',
|
|
99
|
+
});
|
|
100
|
+
(0, vitest_1.expect)(mockFs.existsSync).toHaveBeenCalledWith(customPath);
|
|
101
|
+
(0, vitest_1.expect)(mockWriteFile).toHaveBeenCalledWith(customPath, 'TEST_KEY=test_value\n');
|
|
102
|
+
}));
|
|
103
|
+
(0, vitest_1.it)('should throw error when write fails', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
104
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
105
|
+
mockWriteFile.mockRejectedValue(new Error('Write error'));
|
|
106
|
+
yield (0, vitest_1.expect)((0, env_manager_1.updateEnvFile)({
|
|
107
|
+
key: 'TEST_KEY',
|
|
108
|
+
value: 'test_value',
|
|
109
|
+
})).rejects.toThrow('Write error');
|
|
110
|
+
}));
|
|
111
|
+
});
|
|
112
|
+
(0, vitest_1.describe)('hasEnvKey', () => {
|
|
113
|
+
(0, vitest_1.it)('should return false when .env file does not exist', () => {
|
|
114
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
115
|
+
const result = (0, env_manager_1.hasEnvKey)(testEnvPath, 'TEST_KEY');
|
|
116
|
+
(0, vitest_1.expect)(result).toBe(false);
|
|
117
|
+
(0, vitest_1.expect)(mockFs.existsSync).toHaveBeenCalledWith(testEnvPath);
|
|
118
|
+
(0, vitest_1.expect)(mockFs.readFileSync).not.toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
(0, vitest_1.it)('should return true when key exists in .env file', () => {
|
|
121
|
+
const existingContent = 'KEY1=value1\nTEST_KEY=test_value\nKEY2=value2\n';
|
|
122
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
123
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
124
|
+
const result = (0, env_manager_1.hasEnvKey)(testEnvPath, 'TEST_KEY');
|
|
125
|
+
(0, vitest_1.expect)(result).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
(0, vitest_1.it)('should return false when key does not exist in .env file', () => {
|
|
128
|
+
const existingContent = 'KEY1=value1\nKEY2=value2\n';
|
|
129
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
130
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
131
|
+
const result = (0, env_manager_1.hasEnvKey)(testEnvPath, 'TEST_KEY');
|
|
132
|
+
(0, vitest_1.expect)(result).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
(0, vitest_1.it)('should return false when .env file is malformed', () => {
|
|
135
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
136
|
+
mockFs.readFileSync.mockImplementation(() => {
|
|
137
|
+
throw new Error('Read error');
|
|
138
|
+
});
|
|
139
|
+
const result = (0, env_manager_1.hasEnvKey)(testEnvPath, 'TEST_KEY');
|
|
140
|
+
(0, vitest_1.expect)(result).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
(0, vitest_1.it)('should use default path when not provided', () => {
|
|
143
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
144
|
+
(0, env_manager_1.hasEnvKey)(undefined, 'TEST_KEY');
|
|
145
|
+
(0, vitest_1.expect)(mockFs.existsSync).toHaveBeenCalledWith(testEnvPath);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const opencode_validator_1 = require("../../src/utils/opencode-validator");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
(0, vitest_1.describe)('OpenCode Validator', () => {
|
|
7
|
+
(0, vitest_1.it)('should validate a correct OpenCode configuration', () => {
|
|
8
|
+
const validConfig = {
|
|
9
|
+
$schema: 'https://opencode.ai/config.json',
|
|
10
|
+
username: 'test-user',
|
|
11
|
+
model: 'gpt-4',
|
|
12
|
+
agent: {
|
|
13
|
+
test: {
|
|
14
|
+
model: 'gpt-4',
|
|
15
|
+
temperature: 0.7,
|
|
16
|
+
prompt: 'Test agent',
|
|
17
|
+
permission: {
|
|
18
|
+
edit: 'allow',
|
|
19
|
+
bash: 'allow',
|
|
20
|
+
webfetch: 'allow',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const result = (0, opencode_validator_1.validateOpenCodeConfig)(validConfig);
|
|
26
|
+
(0, vitest_1.expect)(result.valid).toBe(true);
|
|
27
|
+
(0, vitest_1.expect)(result.errors).toBeUndefined();
|
|
28
|
+
});
|
|
29
|
+
(0, vitest_1.it)('should reject invalid configuration', () => {
|
|
30
|
+
const invalidConfig = {
|
|
31
|
+
username: 123, // Should be string
|
|
32
|
+
model: 'gpt-4',
|
|
33
|
+
agent: {
|
|
34
|
+
test: {
|
|
35
|
+
model: 'gpt-4',
|
|
36
|
+
temperature: 'high', // Should be number
|
|
37
|
+
prompt: 'Test agent',
|
|
38
|
+
permission: {
|
|
39
|
+
edit: 'invalid', // Should be enum value
|
|
40
|
+
bash: 'allow',
|
|
41
|
+
webfetch: 'allow',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
const result = (0, opencode_validator_1.validateOpenCodeConfig)(invalidConfig);
|
|
47
|
+
(0, vitest_1.expect)(result.valid).toBe(false);
|
|
48
|
+
(0, vitest_1.expect)(result.errors).toBeDefined();
|
|
49
|
+
(0, vitest_1.expect)(result.errors.length).toBeGreaterThan(0);
|
|
50
|
+
});
|
|
51
|
+
(0, vitest_1.it)('should fix common configuration issues', () => {
|
|
52
|
+
const configWithIssues = {
|
|
53
|
+
username: 'test-user',
|
|
54
|
+
model: 'gpt-4',
|
|
55
|
+
tools: {
|
|
56
|
+
compact: { threshold: 80000 }, // Should be boolean
|
|
57
|
+
},
|
|
58
|
+
maxTokens: 4000, // Invalid property
|
|
59
|
+
provider: {
|
|
60
|
+
berget: {
|
|
61
|
+
models: {
|
|
62
|
+
'test-model': {
|
|
63
|
+
name: 'Test Model',
|
|
64
|
+
maxTokens: 4000, // Should be moved to limit.context
|
|
65
|
+
contextWindow: 8000, // Should be moved to limit.context
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
const fixed = (0, opencode_validator_1.fixOpenCodeConfig)(configWithIssues);
|
|
72
|
+
// tools.compact should be boolean
|
|
73
|
+
(0, vitest_1.expect)(typeof fixed.tools.compact).toBe('boolean');
|
|
74
|
+
// maxTokens should be removed
|
|
75
|
+
(0, vitest_1.expect)(fixed.maxTokens).toBeUndefined();
|
|
76
|
+
// maxTokens and contextWindow should be moved to limit.context
|
|
77
|
+
(0, vitest_1.expect)(fixed.provider.berget.models['test-model'].limit).toBeDefined();
|
|
78
|
+
(0, vitest_1.expect)(fixed.provider.berget.models['test-model'].limit.context).toBe(8000);
|
|
79
|
+
(0, vitest_1.expect)(fixed.provider.berget.models['test-model'].maxTokens).toBeUndefined();
|
|
80
|
+
(0, vitest_1.expect)(fixed.provider.berget.models['test-model'].contextWindow).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
(0, vitest_1.it)('should validate the current opencode.json file', () => {
|
|
83
|
+
var _a;
|
|
84
|
+
try {
|
|
85
|
+
const currentConfig = JSON.parse((0, fs_1.readFileSync)('opencode.json', 'utf8'));
|
|
86
|
+
// Apply fixes to handle common issues
|
|
87
|
+
const fixedConfig = (0, opencode_validator_1.fixOpenCodeConfig)(currentConfig);
|
|
88
|
+
// Validate the fixed config
|
|
89
|
+
const result = (0, opencode_validator_1.validateOpenCodeConfig)(fixedConfig);
|
|
90
|
+
// The fixed config should be valid according to the JSON Schema
|
|
91
|
+
(0, vitest_1.expect)(result.valid).toBe(true);
|
|
92
|
+
if (!result.valid) {
|
|
93
|
+
console.log('Fixed opencode.json validation errors:');
|
|
94
|
+
(_a = result.errors) === null || _a === void 0 ? void 0 : _a.forEach((err) => console.log(` - ${err}`));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
// If we can't read the file, that's ok for this test
|
|
99
|
+
console.log('Could not read opencode.json for testing:', error);
|
|
100
|
+
vitest_1.expect.fail('Should be able to read opencode.json');
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|