Skip to content

Commit 476d835

Browse files
CLI - Fix compatibility with 4.107.0 (#3168)
* refactor: Improve Extension error logging & Resolve compatibility problems * refactor: Add more required vscode mocks * fix: Fix config creation that need bypass the config validation * refactor: add changeset * Update cli/src/services/logs.ts Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent 712b104 commit 476d835

File tree

9 files changed

+385
-9
lines changed

9 files changed

+385
-9
lines changed

.changeset/old-rivers-pay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@kilocode/cli": patch
3+
---
4+
5+
Fix compatibility with extension v4.107.0
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"
2+
import * as fs from "fs/promises"
3+
import * as path from "path"
4+
import { homedir } from "os"
5+
import { saveConfig, setConfigPaths, resetConfigPaths, ensureConfigDir } from "../config/persistence.js"
6+
import { DEFAULT_CONFIG } from "../config/defaults.js"
7+
8+
// Mock the logs service
9+
vi.mock("../services/logs.js", () => ({
10+
logs: {
11+
info: vi.fn(),
12+
debug: vi.fn(),
13+
error: vi.fn(),
14+
warn: vi.fn(),
15+
},
16+
}))
17+
18+
// Mock fs/promises to handle schema.json reads
19+
vi.mock("fs/promises", async () => {
20+
const actual = await vi.importActual<typeof import("fs/promises")>("fs/promises")
21+
return {
22+
...actual,
23+
readFile: vi.fn(async (filePath: any, encoding?: any) => {
24+
// If reading schema.json, return a minimal valid schema
25+
if (typeof filePath === "string" && filePath.includes("schema.json")) {
26+
return JSON.stringify({
27+
type: "object",
28+
properties: {},
29+
additionalProperties: true,
30+
})
31+
}
32+
// Otherwise use the actual implementation
33+
return actual.readFile(filePath, encoding)
34+
}),
35+
}
36+
})
37+
38+
// Define test paths
39+
const TEST_CONFIG_DIR = path.join(homedir(), ".kilocode", "cli-test-default")
40+
const TEST_CONFIG_FILE = path.join(TEST_CONFIG_DIR, "config.json")
41+
42+
describe("Default Config Creation", () => {
43+
beforeEach(async () => {
44+
// Set test config paths
45+
setConfigPaths(TEST_CONFIG_DIR, TEST_CONFIG_FILE)
46+
47+
// Clean up test directory before each test
48+
try {
49+
await fs.rm(TEST_CONFIG_DIR, { recursive: true, force: true })
50+
} catch {
51+
// Ignore if doesn't exist
52+
}
53+
})
54+
55+
afterEach(async () => {
56+
// Reset config paths
57+
resetConfigPaths()
58+
59+
// Clean up test directory after each test
60+
try {
61+
await fs.rm(TEST_CONFIG_DIR, { recursive: true, force: true })
62+
} catch {
63+
// Ignore if doesn't exist
64+
}
65+
})
66+
67+
it("should create default config without validation when skipValidation is true", async () => {
68+
// Ensure directory exists
69+
await ensureConfigDir()
70+
71+
// Save default config with skipValidation=true (simulating 'kilocode config' command)
72+
// This should succeed even though DEFAULT_CONFIG has empty tokens
73+
await expect(saveConfig(DEFAULT_CONFIG, true)).resolves.not.toThrow()
74+
75+
// Verify file was created
76+
const content = await fs.readFile(TEST_CONFIG_FILE, "utf-8")
77+
const parsed = JSON.parse(content)
78+
expect(parsed).toEqual(DEFAULT_CONFIG)
79+
})
80+
81+
it("should fail to save default config when validation is enabled", async () => {
82+
// Ensure directory exists
83+
await ensureConfigDir()
84+
85+
// Save default config with skipValidation=false (default)
86+
// This should fail because DEFAULT_CONFIG has empty tokens
87+
await expect(saveConfig(DEFAULT_CONFIG, false)).rejects.toThrow(/Invalid config/)
88+
})
89+
90+
it("should fail to save default config when skipValidation is not provided", async () => {
91+
// Ensure directory exists
92+
await ensureConfigDir()
93+
94+
// Save default config without skipValidation parameter (defaults to false)
95+
// This should fail because DEFAULT_CONFIG has empty tokens
96+
await expect(saveConfig(DEFAULT_CONFIG)).rejects.toThrow(/Invalid config/)
97+
})
98+
})

cli/src/config/persistence.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,16 @@ export async function loadConfig(): Promise<ConfigLoadResult> {
166166
}
167167
}
168168

169-
export async function saveConfig(config: CLIConfig): Promise<void> {
169+
export async function saveConfig(config: CLIConfig, skipValidation: boolean = false): Promise<void> {
170170
try {
171171
await ensureConfigDir()
172172

173-
// Validate before saving
174-
const validation = await validateConfig(config)
175-
if (!validation.valid) {
176-
throw new Error(`Invalid config: ${validation.errors?.join(", ")}`)
173+
// Validate before saving (unless explicitly skipped for initial config creation)
174+
if (!skipValidation) {
175+
const validation = await validateConfig(config)
176+
if (!validation.valid) {
177+
throw new Error(`Invalid config: ${validation.errors?.join(", ")}`)
178+
}
177179
}
178180

179181
// Write config with pretty formatting

cli/src/host/ExtensionHost.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ export class ExtensionHost extends EventEmitter {
501501
logs.info("Calling extension activate function...", "ExtensionHost")
502502

503503
// Call the extension's activate function with our mocked context
504+
// Use safeExecute to catch and handle any errors without crashing the CLI
504505
this.extensionAPI = await this.safeExecute(
505506
async () => await this.extensionModule.activate(this.vscodeAPI.context),
506507
"extension.activate",

0 commit comments

Comments
 (0)