berget 2.2.6 → 2.2.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/.github/workflows/publish.yml +6 -6
- package/.github/workflows/test.yml +11 -5
- package/.husky/pre-commit +1 -0
- package/.prettierignore +15 -0
- package/.prettierrc +5 -3
- package/CONTRIBUTING.md +38 -0
- package/README.md +2 -148
- package/dist/index.js +21 -21
- package/dist/package.json +28 -2
- package/dist/src/agents/app.js +28 -0
- package/dist/src/agents/backend.js +25 -0
- package/dist/src/agents/devops.js +34 -0
- package/dist/src/agents/frontend.js +25 -0
- package/dist/src/agents/fullstack.js +25 -0
- package/dist/src/agents/index.js +61 -0
- package/dist/src/agents/quality.js +70 -0
- package/dist/src/agents/security.js +26 -0
- package/dist/src/agents/types.js +2 -0
- package/dist/src/client.js +54 -62
- package/dist/src/commands/api-keys.js +132 -140
- package/dist/src/commands/auth.js +9 -9
- package/dist/src/commands/autocomplete.js +9 -9
- package/dist/src/commands/billing.js +7 -9
- package/dist/src/commands/chat.js +90 -92
- package/dist/src/commands/clusters.js +12 -12
- package/dist/src/commands/code/__tests__/auth-sync.test.js +348 -0
- package/dist/src/commands/code/__tests__/fake-api-key-service.js +23 -0
- package/dist/src/commands/code/__tests__/fake-auth-service.js +55 -0
- package/dist/src/commands/code/__tests__/fake-command-runner.js +5 -7
- package/dist/src/commands/code/__tests__/fake-file-store.js +9 -0
- package/dist/src/commands/code/__tests__/fake-prompter.js +60 -18
- package/dist/src/commands/code/__tests__/setup-flow.test.js +374 -107
- package/dist/src/commands/code/adapters/clack-prompter.js +10 -0
- package/dist/src/commands/code/adapters/fs-file-store.js +8 -3
- package/dist/src/commands/code/adapters/spawn-command-runner.js +15 -11
- package/dist/src/commands/code/auth-sync.js +283 -0
- package/dist/src/commands/code/errors.js +4 -4
- package/dist/src/commands/code/ports/auth-services.js +2 -0
- package/dist/src/commands/code/setup.js +234 -93
- package/dist/src/commands/code.js +139 -251
- package/dist/src/commands/models.js +13 -15
- package/dist/src/commands/users.js +6 -8
- package/dist/src/constants/command-structure.js +116 -116
- package/dist/src/services/api-key-service.js +43 -48
- package/dist/src/services/auth-service.js +60 -299
- package/dist/src/services/browser-auth.js +278 -0
- package/dist/src/services/chat-service.js +78 -91
- package/dist/src/services/cluster-service.js +6 -6
- package/dist/src/services/collaborator-service.js +5 -8
- package/dist/src/services/flux-service.js +5 -8
- package/dist/src/services/helm-service.js +5 -8
- package/dist/src/services/kubectl-service.js +7 -10
- package/dist/src/utils/config-checker.js +5 -5
- package/dist/src/utils/config-loader.js +25 -25
- package/dist/src/utils/default-api-key.js +23 -23
- package/dist/src/utils/env-manager.js +7 -7
- package/dist/src/utils/error-handler.js +60 -61
- package/dist/src/utils/logger.js +7 -7
- package/dist/src/utils/markdown-renderer.js +2 -2
- package/dist/src/utils/opencode-validator.js +17 -20
- package/dist/src/utils/token-manager.js +38 -11
- package/dist/tests/commands/chat.test.js +24 -24
- package/dist/tests/commands/code.test.js +147 -147
- package/dist/tests/utils/config-loader.test.js +114 -114
- package/dist/tests/utils/env-manager.test.js +57 -57
- package/dist/tests/utils/opencode-validator.test.js +33 -33
- package/dist/vitest.config.js +1 -1
- package/eslint.config.mjs +47 -0
- package/index.ts +42 -48
- package/package.json +28 -2
- package/src/agents/app.ts +27 -0
- package/src/agents/backend.ts +24 -0
- package/src/agents/devops.ts +33 -0
- package/src/agents/frontend.ts +24 -0
- package/src/agents/fullstack.ts +24 -0
- package/src/agents/index.ts +71 -0
- package/src/agents/quality.ts +69 -0
- package/src/agents/security.ts +26 -0
- package/src/agents/types.ts +17 -0
- package/src/client.ts +125 -167
- package/src/commands/api-keys.ts +261 -358
- package/src/commands/auth.ts +24 -30
- package/src/commands/autocomplete.ts +12 -12
- package/src/commands/billing.ts +22 -27
- package/src/commands/chat.ts +230 -323
- package/src/commands/clusters.ts +33 -33
- package/src/commands/code/__tests__/auth-sync.test.ts +481 -0
- package/src/commands/code/__tests__/fake-api-key-service.ts +13 -0
- package/src/commands/code/__tests__/fake-auth-service.ts +50 -0
- package/src/commands/code/__tests__/fake-command-runner.ts +39 -42
- package/src/commands/code/__tests__/fake-file-store.ts +32 -23
- package/src/commands/code/__tests__/fake-prompter.ts +107 -69
- package/src/commands/code/__tests__/setup-flow.test.ts +624 -270
- package/src/commands/code/adapters/clack-prompter.ts +50 -38
- package/src/commands/code/adapters/fs-file-store.ts +31 -27
- package/src/commands/code/adapters/spawn-command-runner.ts +33 -29
- package/src/commands/code/auth-sync.ts +329 -0
- package/src/commands/code/errors.ts +15 -15
- package/src/commands/code/ports/auth-services.ts +14 -0
- package/src/commands/code/ports/command-runner.ts +8 -4
- package/src/commands/code/ports/file-store.ts +5 -4
- package/src/commands/code/ports/prompter.ts +24 -18
- package/src/commands/code/setup.ts +545 -317
- package/src/commands/code.ts +271 -473
- package/src/commands/index.ts +19 -19
- package/src/commands/models.ts +32 -37
- package/src/commands/users.ts +15 -22
- package/src/constants/command-structure.ts +119 -142
- package/src/services/api-key-service.ts +96 -113
- package/src/services/auth-service.ts +92 -339
- package/src/services/browser-auth.ts +296 -0
- package/src/services/chat-service.ts +246 -279
- package/src/services/cluster-service.ts +29 -32
- package/src/services/collaborator-service.ts +13 -18
- package/src/services/flux-service.ts +16 -18
- package/src/services/helm-service.ts +16 -18
- package/src/services/kubectl-service.ts +12 -14
- package/src/types/api.d.ts +924 -926
- package/src/types/json.d.ts +3 -3
- package/src/utils/config-checker.ts +10 -10
- package/src/utils/config-loader.ts +110 -127
- package/src/utils/default-api-key.ts +81 -93
- package/src/utils/env-manager.ts +36 -40
- package/src/utils/error-handler.ts +83 -78
- package/src/utils/logger.ts +41 -41
- package/src/utils/markdown-renderer.ts +11 -11
- package/src/utils/opencode-validator.ts +51 -56
- package/src/utils/token-manager.ts +84 -64
- package/templates/agents/app.md +1 -0
- package/templates/agents/backend.md +1 -0
- package/templates/agents/devops.md +2 -0
- package/templates/agents/frontend.md +1 -0
- package/templates/agents/fullstack.md +1 -0
- package/templates/agents/quality.md +45 -40
- package/templates/agents/security.md +1 -0
- package/tests/commands/chat.test.ts +60 -70
- package/tests/commands/code.test.ts +330 -376
- package/tests/utils/config-loader.test.ts +260 -260
- package/tests/utils/env-manager.test.ts +127 -134
- package/tests/utils/opencode-validator.test.ts +58 -63
- package/tsconfig.json +2 -2
- package/vitest.config.ts +3 -3
- package/AGENTS.md +0 -374
- package/TODO.md +0 -19
|
@@ -1,199 +1,192 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from
|
|
2
|
-
import fs from
|
|
3
|
-
import { writeFile } from
|
|
4
|
-
import path from
|
|
5
|
-
import { updateEnvFile, hasEnvKey } from
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { writeFile } from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { updateEnvFile, hasEnvKey } from "../../src/utils/env-manager";
|
|
6
6
|
|
|
7
|
-
vi.mock(
|
|
8
|
-
vi.mock(
|
|
9
|
-
vi.mock(
|
|
7
|
+
vi.mock("fs");
|
|
8
|
+
vi.mock("fs/promises");
|
|
9
|
+
vi.mock("path");
|
|
10
10
|
|
|
11
|
-
const mockFs = vi.mocked(fs)
|
|
12
|
-
const mockWriteFile = vi.mocked(writeFile)
|
|
13
|
-
const mockPath = vi.mocked(path)
|
|
11
|
+
const mockFs = vi.mocked(fs);
|
|
12
|
+
const mockWriteFile = vi.mocked(writeFile);
|
|
13
|
+
const mockPath = vi.mocked(path);
|
|
14
14
|
|
|
15
|
-
describe(
|
|
16
|
-
const testEnvPath =
|
|
17
|
-
const testCwd =
|
|
15
|
+
describe("env-manager", () => {
|
|
16
|
+
const testEnvPath = "/test/.env";
|
|
17
|
+
const testCwd = "/test";
|
|
18
18
|
|
|
19
19
|
beforeEach(() => {
|
|
20
|
-
vi.clearAllMocks()
|
|
21
|
-
mockPath.join.mockReturnValue(testEnvPath)
|
|
22
|
-
vi.spyOn(process,
|
|
23
|
-
})
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
mockPath.join.mockReturnValue(testEnvPath);
|
|
22
|
+
vi.spyOn(process, "cwd").mockReturnValue(testCwd);
|
|
23
|
+
});
|
|
24
24
|
|
|
25
25
|
afterEach(() => {
|
|
26
|
-
vi.restoreAllMocks()
|
|
27
|
-
})
|
|
26
|
+
vi.restoreAllMocks();
|
|
27
|
+
});
|
|
28
28
|
|
|
29
|
-
describe(
|
|
30
|
-
it(
|
|
31
|
-
mockFs.existsSync.mockReturnValue(false)
|
|
29
|
+
describe("updateEnvFile", () => {
|
|
30
|
+
it("should create a new .env file with the key when file does not exist", async () => {
|
|
31
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
32
32
|
|
|
33
33
|
await updateEnvFile({
|
|
34
|
-
key:
|
|
35
|
-
value:
|
|
36
|
-
comment:
|
|
37
|
-
})
|
|
34
|
+
key: "TEST_KEY",
|
|
35
|
+
value: "test_value",
|
|
36
|
+
comment: "Test comment",
|
|
37
|
+
});
|
|
38
38
|
|
|
39
|
-
expect(mockFs.existsSync).toHaveBeenCalledWith(testEnvPath)
|
|
39
|
+
expect(mockFs.existsSync).toHaveBeenCalledWith(testEnvPath);
|
|
40
40
|
expect(mockWriteFile).toHaveBeenCalledWith(
|
|
41
41
|
testEnvPath,
|
|
42
|
-
|
|
43
|
-
)
|
|
44
|
-
})
|
|
42
|
+
"# Test comment\nTEST_KEY=test_value\n"
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
45
|
|
|
46
|
-
it(
|
|
47
|
-
const existingContent =
|
|
48
|
-
mockFs.existsSync.mockReturnValue(true)
|
|
49
|
-
mockFs.readFileSync.mockReturnValue(existingContent)
|
|
46
|
+
it("should append to existing .env file when key does not exist", async () => {
|
|
47
|
+
const existingContent = "EXISTING_KEY=existing_value\n";
|
|
48
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
49
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
50
50
|
|
|
51
51
|
await updateEnvFile({
|
|
52
|
-
key:
|
|
53
|
-
value:
|
|
54
|
-
comment:
|
|
55
|
-
})
|
|
52
|
+
key: "NEW_KEY",
|
|
53
|
+
value: "new_value",
|
|
54
|
+
comment: "Test comment",
|
|
55
|
+
});
|
|
56
56
|
|
|
57
57
|
expect(mockWriteFile).toHaveBeenCalledWith(
|
|
58
58
|
testEnvPath,
|
|
59
|
-
|
|
60
|
-
)
|
|
61
|
-
})
|
|
59
|
+
"EXISTING_KEY=existing_value\nNEW_KEY=new_value\n"
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
62
|
|
|
63
|
-
it(
|
|
64
|
-
const existingContent =
|
|
65
|
-
|
|
66
|
-
mockFs.
|
|
67
|
-
mockFs.readFileSync.mockReturnValue(existingContent)
|
|
63
|
+
it("should not update when key already exists and force is false", async () => {
|
|
64
|
+
const existingContent = "EXISTING_KEY=existing_value\nTEST_KEY=old_value\n";
|
|
65
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
66
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
68
67
|
|
|
69
|
-
const consoleSpy = vi.spyOn(console,
|
|
68
|
+
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
70
69
|
|
|
71
70
|
await updateEnvFile({
|
|
72
|
-
key:
|
|
73
|
-
value:
|
|
74
|
-
})
|
|
71
|
+
key: "TEST_KEY",
|
|
72
|
+
value: "new_value",
|
|
73
|
+
});
|
|
75
74
|
|
|
76
75
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
77
|
-
expect.stringContaining(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
expect(mockWriteFile).not.toHaveBeenCalled()
|
|
76
|
+
expect.stringContaining("TEST_KEY already exists in .env - leaving unchanged")
|
|
77
|
+
);
|
|
78
|
+
expect(mockWriteFile).not.toHaveBeenCalled();
|
|
82
79
|
|
|
83
|
-
consoleSpy.mockRestore()
|
|
84
|
-
})
|
|
80
|
+
consoleSpy.mockRestore();
|
|
81
|
+
});
|
|
85
82
|
|
|
86
|
-
it(
|
|
87
|
-
const existingContent =
|
|
88
|
-
|
|
89
|
-
mockFs.
|
|
90
|
-
mockFs.readFileSync.mockReturnValue(existingContent)
|
|
83
|
+
it("should update existing key when force is true", async () => {
|
|
84
|
+
const existingContent = "EXISTING_KEY=existing_value\nTEST_KEY=old_value\n";
|
|
85
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
86
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
91
87
|
|
|
92
88
|
await updateEnvFile({
|
|
93
|
-
key:
|
|
94
|
-
value:
|
|
89
|
+
key: "TEST_KEY",
|
|
90
|
+
value: "new_value",
|
|
95
91
|
force: true,
|
|
96
|
-
})
|
|
92
|
+
});
|
|
97
93
|
|
|
98
94
|
expect(mockWriteFile).toHaveBeenCalledWith(
|
|
99
95
|
testEnvPath,
|
|
100
|
-
|
|
101
|
-
)
|
|
102
|
-
})
|
|
96
|
+
"EXISTING_KEY=existing_value\nTEST_KEY=new_value\n"
|
|
97
|
+
);
|
|
98
|
+
});
|
|
103
99
|
|
|
104
|
-
it(
|
|
105
|
-
mockFs.existsSync.mockReturnValue(false)
|
|
100
|
+
it("should handle complex values with quotes and special characters", async () => {
|
|
101
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
106
102
|
|
|
107
103
|
await updateEnvFile({
|
|
108
|
-
key:
|
|
104
|
+
key: "COMPLEX_KEY",
|
|
109
105
|
value: 'value with "quotes" and $special',
|
|
110
|
-
comment:
|
|
111
|
-
})
|
|
106
|
+
comment: "Complex test",
|
|
107
|
+
});
|
|
112
108
|
|
|
113
109
|
expect(mockWriteFile).toHaveBeenCalledWith(
|
|
114
110
|
testEnvPath,
|
|
115
|
-
'# Complex test\nCOMPLEX_KEY=value with "quotes" and $special\n'
|
|
116
|
-
)
|
|
117
|
-
})
|
|
111
|
+
'# Complex test\nCOMPLEX_KEY=value with "quotes" and $special\n'
|
|
112
|
+
);
|
|
113
|
+
});
|
|
118
114
|
|
|
119
|
-
it(
|
|
120
|
-
const customPath =
|
|
121
|
-
mockFs.existsSync.mockReturnValue(false)
|
|
115
|
+
it("should use custom env path when provided", async () => {
|
|
116
|
+
const customPath = "/custom/.env";
|
|
117
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
122
118
|
|
|
123
119
|
await updateEnvFile({
|
|
124
120
|
envPath: customPath,
|
|
125
|
-
key:
|
|
126
|
-
value:
|
|
127
|
-
})
|
|
121
|
+
key: "TEST_KEY",
|
|
122
|
+
value: "test_value",
|
|
123
|
+
});
|
|
128
124
|
|
|
129
|
-
expect(mockFs.existsSync).toHaveBeenCalledWith(customPath)
|
|
130
|
-
expect(mockWriteFile).toHaveBeenCalledWith(
|
|
131
|
-
|
|
132
|
-
'TEST_KEY=test_value\n',
|
|
133
|
-
)
|
|
134
|
-
})
|
|
125
|
+
expect(mockFs.existsSync).toHaveBeenCalledWith(customPath);
|
|
126
|
+
expect(mockWriteFile).toHaveBeenCalledWith(customPath, "TEST_KEY=test_value\n");
|
|
127
|
+
});
|
|
135
128
|
|
|
136
|
-
it(
|
|
137
|
-
mockFs.existsSync.mockReturnValue(false)
|
|
138
|
-
mockWriteFile.mockRejectedValue(new Error(
|
|
129
|
+
it("should throw error when write fails", async () => {
|
|
130
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
131
|
+
mockWriteFile.mockRejectedValue(new Error("Write error"));
|
|
139
132
|
|
|
140
133
|
await expect(
|
|
141
134
|
updateEnvFile({
|
|
142
|
-
key:
|
|
143
|
-
value:
|
|
144
|
-
})
|
|
145
|
-
).rejects.toThrow(
|
|
146
|
-
})
|
|
147
|
-
})
|
|
135
|
+
key: "TEST_KEY",
|
|
136
|
+
value: "test_value",
|
|
137
|
+
})
|
|
138
|
+
).rejects.toThrow("Write error");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
148
141
|
|
|
149
|
-
describe(
|
|
150
|
-
it(
|
|
151
|
-
mockFs.existsSync.mockReturnValue(false)
|
|
142
|
+
describe("hasEnvKey", () => {
|
|
143
|
+
it("should return false when .env file does not exist", () => {
|
|
144
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
152
145
|
|
|
153
|
-
const result = hasEnvKey(testEnvPath,
|
|
146
|
+
const result = hasEnvKey(testEnvPath, "TEST_KEY");
|
|
154
147
|
|
|
155
|
-
expect(result).toBe(false)
|
|
156
|
-
expect(mockFs.existsSync).toHaveBeenCalledWith(testEnvPath)
|
|
157
|
-
expect(mockFs.readFileSync).not.toHaveBeenCalled()
|
|
158
|
-
})
|
|
148
|
+
expect(result).toBe(false);
|
|
149
|
+
expect(mockFs.existsSync).toHaveBeenCalledWith(testEnvPath);
|
|
150
|
+
expect(mockFs.readFileSync).not.toHaveBeenCalled();
|
|
151
|
+
});
|
|
159
152
|
|
|
160
|
-
it(
|
|
161
|
-
const existingContent =
|
|
162
|
-
mockFs.existsSync.mockReturnValue(true)
|
|
163
|
-
mockFs.readFileSync.mockReturnValue(existingContent)
|
|
153
|
+
it("should return true when key exists in .env file", () => {
|
|
154
|
+
const existingContent = "KEY1=value1\nTEST_KEY=test_value\nKEY2=value2\n";
|
|
155
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
156
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
164
157
|
|
|
165
|
-
const result = hasEnvKey(testEnvPath,
|
|
158
|
+
const result = hasEnvKey(testEnvPath, "TEST_KEY");
|
|
166
159
|
|
|
167
|
-
expect(result).toBe(true)
|
|
168
|
-
})
|
|
160
|
+
expect(result).toBe(true);
|
|
161
|
+
});
|
|
169
162
|
|
|
170
|
-
it(
|
|
171
|
-
const existingContent =
|
|
172
|
-
mockFs.existsSync.mockReturnValue(true)
|
|
173
|
-
mockFs.readFileSync.mockReturnValue(existingContent)
|
|
163
|
+
it("should return false when key does not exist in .env file", () => {
|
|
164
|
+
const existingContent = "KEY1=value1\nKEY2=value2\n";
|
|
165
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
166
|
+
mockFs.readFileSync.mockReturnValue(existingContent);
|
|
174
167
|
|
|
175
|
-
const result = hasEnvKey(testEnvPath,
|
|
168
|
+
const result = hasEnvKey(testEnvPath, "TEST_KEY");
|
|
176
169
|
|
|
177
|
-
expect(result).toBe(false)
|
|
178
|
-
})
|
|
170
|
+
expect(result).toBe(false);
|
|
171
|
+
});
|
|
179
172
|
|
|
180
|
-
it(
|
|
181
|
-
mockFs.existsSync.mockReturnValue(true)
|
|
173
|
+
it("should return false when .env file is malformed", () => {
|
|
174
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
182
175
|
mockFs.readFileSync.mockImplementation(() => {
|
|
183
|
-
throw new Error(
|
|
184
|
-
})
|
|
176
|
+
throw new Error("Read error");
|
|
177
|
+
});
|
|
185
178
|
|
|
186
|
-
const result = hasEnvKey(testEnvPath,
|
|
179
|
+
const result = hasEnvKey(testEnvPath, "TEST_KEY");
|
|
187
180
|
|
|
188
|
-
expect(result).toBe(false)
|
|
189
|
-
})
|
|
181
|
+
expect(result).toBe(false);
|
|
182
|
+
});
|
|
190
183
|
|
|
191
|
-
it(
|
|
192
|
-
mockFs.existsSync.mockReturnValue(false)
|
|
184
|
+
it("should use default path when not provided", () => {
|
|
185
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
193
186
|
|
|
194
|
-
hasEnvKey(undefined,
|
|
187
|
+
hasEnvKey(undefined, "TEST_KEY");
|
|
195
188
|
|
|
196
|
-
expect(mockFs.existsSync).toHaveBeenCalledWith(testEnvPath)
|
|
197
|
-
})
|
|
198
|
-
})
|
|
199
|
-
})
|
|
189
|
+
expect(mockFs.existsSync).toHaveBeenCalledWith(testEnvPath);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -1,63 +1,60 @@
|
|
|
1
|
-
import { describe, it, expect } from
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
fixOpenCodeConfig,
|
|
5
|
-
} from '../../src/utils/opencode-validator'
|
|
6
|
-
import { readFileSync } from 'fs'
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { validateOpenCodeConfig, fixOpenCodeConfig } from "../../src/utils/opencode-validator";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
7
4
|
|
|
8
|
-
describe(
|
|
9
|
-
it(
|
|
5
|
+
describe("OpenCode Validator", () => {
|
|
6
|
+
it("should validate a correct OpenCode configuration", () => {
|
|
10
7
|
const validConfig = {
|
|
11
|
-
$schema:
|
|
12
|
-
username:
|
|
13
|
-
model:
|
|
8
|
+
$schema: "https://opencode.ai/config.json",
|
|
9
|
+
username: "test-user",
|
|
10
|
+
model: "gpt-4",
|
|
14
11
|
agent: {
|
|
15
12
|
test: {
|
|
16
|
-
model:
|
|
13
|
+
model: "gpt-4",
|
|
17
14
|
temperature: 0.7,
|
|
18
|
-
prompt:
|
|
15
|
+
prompt: "Test agent",
|
|
19
16
|
permission: {
|
|
20
|
-
edit:
|
|
21
|
-
bash:
|
|
22
|
-
webfetch:
|
|
17
|
+
edit: "allow",
|
|
18
|
+
bash: "allow",
|
|
19
|
+
webfetch: "allow",
|
|
23
20
|
},
|
|
24
21
|
},
|
|
25
22
|
},
|
|
26
|
-
}
|
|
23
|
+
};
|
|
27
24
|
|
|
28
|
-
const result = validateOpenCodeConfig(validConfig)
|
|
29
|
-
expect(result.valid).toBe(true)
|
|
30
|
-
expect(result.errors).toBeUndefined()
|
|
31
|
-
})
|
|
25
|
+
const result = validateOpenCodeConfig(validConfig);
|
|
26
|
+
expect(result.valid).toBe(true);
|
|
27
|
+
expect(result.errors).toBeUndefined();
|
|
28
|
+
});
|
|
32
29
|
|
|
33
|
-
it(
|
|
30
|
+
it("should reject invalid configuration", () => {
|
|
34
31
|
const invalidConfig = {
|
|
35
32
|
username: 123, // Should be string
|
|
36
|
-
model:
|
|
33
|
+
model: "gpt-4",
|
|
37
34
|
agent: {
|
|
38
35
|
test: {
|
|
39
|
-
model:
|
|
40
|
-
temperature:
|
|
41
|
-
prompt:
|
|
36
|
+
model: "gpt-4",
|
|
37
|
+
temperature: "high", // Should be number
|
|
38
|
+
prompt: "Test agent",
|
|
42
39
|
permission: {
|
|
43
|
-
edit:
|
|
44
|
-
bash:
|
|
45
|
-
webfetch:
|
|
40
|
+
edit: "invalid", // Should be enum value
|
|
41
|
+
bash: "allow",
|
|
42
|
+
webfetch: "allow",
|
|
46
43
|
},
|
|
47
44
|
},
|
|
48
45
|
},
|
|
49
|
-
}
|
|
46
|
+
};
|
|
50
47
|
|
|
51
|
-
const result = validateOpenCodeConfig(invalidConfig)
|
|
52
|
-
expect(result.valid).toBe(false)
|
|
53
|
-
expect(result.errors).toBeDefined()
|
|
54
|
-
expect(result.errors!.length).toBeGreaterThan(0)
|
|
55
|
-
})
|
|
48
|
+
const result = validateOpenCodeConfig(invalidConfig);
|
|
49
|
+
expect(result.valid).toBe(false);
|
|
50
|
+
expect(result.errors).toBeDefined();
|
|
51
|
+
expect(result.errors!.length).toBeGreaterThan(0);
|
|
52
|
+
});
|
|
56
53
|
|
|
57
|
-
it(
|
|
54
|
+
it("should fix common configuration issues", () => {
|
|
58
55
|
const configWithIssues = {
|
|
59
|
-
username:
|
|
60
|
-
model:
|
|
56
|
+
username: "test-user",
|
|
57
|
+
model: "gpt-4",
|
|
61
58
|
tools: {
|
|
62
59
|
compact: { threshold: 80000 }, // Should be boolean
|
|
63
60
|
},
|
|
@@ -65,55 +62,53 @@ describe('OpenCode Validator', () => {
|
|
|
65
62
|
provider: {
|
|
66
63
|
berget: {
|
|
67
64
|
models: {
|
|
68
|
-
|
|
69
|
-
name:
|
|
65
|
+
"test-model": {
|
|
66
|
+
name: "Test Model",
|
|
70
67
|
maxTokens: 4000, // Should be moved to limit.context
|
|
71
68
|
contextWindow: 8000, // Should be moved to limit.context
|
|
72
69
|
},
|
|
73
70
|
},
|
|
74
71
|
},
|
|
75
72
|
},
|
|
76
|
-
}
|
|
73
|
+
};
|
|
77
74
|
|
|
78
|
-
const fixed = fixOpenCodeConfig(configWithIssues)
|
|
75
|
+
const fixed = fixOpenCodeConfig(configWithIssues);
|
|
79
76
|
|
|
80
77
|
// tools.compact should be boolean
|
|
81
|
-
expect(typeof fixed.tools.compact).toBe(
|
|
78
|
+
expect(typeof fixed.tools.compact).toBe("boolean");
|
|
82
79
|
|
|
83
80
|
// maxTokens should be removed
|
|
84
|
-
expect(fixed.maxTokens).toBeUndefined()
|
|
81
|
+
expect(fixed.maxTokens).toBeUndefined();
|
|
85
82
|
|
|
86
83
|
// maxTokens and contextWindow should be moved to limit.context
|
|
87
|
-
expect(fixed.provider.berget.models[
|
|
88
|
-
expect(fixed.provider.berget.models[
|
|
89
|
-
expect(fixed.provider.berget.models[
|
|
90
|
-
expect(
|
|
91
|
-
|
|
92
|
-
).toBeUndefined()
|
|
93
|
-
})
|
|
84
|
+
expect(fixed.provider.berget.models["test-model"].limit).toBeDefined();
|
|
85
|
+
expect(fixed.provider.berget.models["test-model"].limit.context).toBe(8000);
|
|
86
|
+
expect(fixed.provider.berget.models["test-model"].maxTokens).toBeUndefined();
|
|
87
|
+
expect(fixed.provider.berget.models["test-model"].contextWindow).toBeUndefined();
|
|
88
|
+
});
|
|
94
89
|
|
|
95
|
-
it(
|
|
96
|
-
let currentConfig
|
|
90
|
+
it("should validate the current opencode.json file", () => {
|
|
91
|
+
let currentConfig;
|
|
97
92
|
try {
|
|
98
|
-
currentConfig = JSON.parse(readFileSync(
|
|
93
|
+
currentConfig = JSON.parse(readFileSync("opencode.json", "utf8"));
|
|
99
94
|
} catch (error) {
|
|
100
95
|
// Skip when opencode.json is not present (e.g. in CI or clean checkouts)
|
|
101
|
-
console.log(
|
|
102
|
-
return
|
|
96
|
+
console.log("Skipping: opencode.json not found:", error);
|
|
97
|
+
return;
|
|
103
98
|
}
|
|
104
99
|
|
|
105
100
|
// Apply fixes to handle common issues
|
|
106
|
-
const fixedConfig = fixOpenCodeConfig(currentConfig)
|
|
101
|
+
const fixedConfig = fixOpenCodeConfig(currentConfig);
|
|
107
102
|
|
|
108
103
|
// Validate the fixed config
|
|
109
|
-
const result = validateOpenCodeConfig(fixedConfig)
|
|
104
|
+
const result = validateOpenCodeConfig(fixedConfig);
|
|
110
105
|
|
|
111
106
|
// The fixed config should be valid according to the JSON Schema
|
|
112
|
-
expect(result.valid).toBe(true)
|
|
107
|
+
expect(result.valid).toBe(true);
|
|
113
108
|
|
|
114
109
|
if (!result.valid) {
|
|
115
|
-
console.log(
|
|
116
|
-
result.errors?.forEach(
|
|
110
|
+
console.log("Fixed opencode.json validation errors:");
|
|
111
|
+
result.errors?.forEach(err => console.log(` - ${err}`));
|
|
117
112
|
}
|
|
118
|
-
})
|
|
119
|
-
})
|
|
113
|
+
});
|
|
114
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -91,8 +91,8 @@
|
|
|
91
91
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
|
92
92
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
|
93
93
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
"noUnusedLocals": true /* Enable error reporting when local variables aren't read. */,
|
|
95
|
+
"noUnusedParameters": true /* Raise an error when a function parameter isn't read. */,
|
|
96
96
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
|
97
97
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
|
98
98
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|