diff --git a/package-lock.json b/package-lock.json index 70edda5a596..e04ef43f8b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,7 @@ "vscode-material-icons": "^0.1.1", "web-tree-sitter": "^0.22.6", "workerpool": "^9.2.0", + "yaml": "^2.8.0", "zod": "^3.24.2" }, "devDependencies": { @@ -20729,16 +20730,15 @@ "dev": true }, "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", - "dev": true, + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index 2554756b8ae..22a3c0cfa5e 100644 --- a/package.json +++ b/package.json @@ -422,6 +422,7 @@ "vscode-material-icons": "^0.1.1", "web-tree-sitter": "^0.22.6", "workerpool": "^9.2.0", + "yaml": "^2.8.0", "zod": "^3.24.2" }, "devDependencies": { diff --git a/src/core/config/CustomModesManager.ts b/src/core/config/CustomModesManager.ts index eed7dee9485..5b9ba84b366 100644 --- a/src/core/config/CustomModesManager.ts +++ b/src/core/config/CustomModesManager.ts @@ -7,6 +7,7 @@ import { fileExistsAtPath } from "../../utils/fs" import { arePathsEqual, getWorkspacePath } from "../../utils/path" import { logger } from "../../utils/logging" import { GlobalFileNames } from "../../shared/globalFileNames" +import * as yaml from "yaml" const ROOMODES_FILENAME = ".roomodes" @@ -71,7 +72,7 @@ export class CustomModesManager { private async loadModesFromFile(filePath: string): Promise { try { const content = await fs.readFile(filePath, "utf-8") - const settings = JSON.parse(content) + const settings = yaml.parse(content) const result = customModesSettingsSchema.safeParse(settings) if (!result.success) { return [] @@ -140,7 +141,7 @@ export class CustomModesManager { let config: any try { - config = JSON.parse(content) + config = yaml.parse(content) } catch (error) { console.error(error) vscode.window.showErrorMessage(errorMessage) @@ -296,7 +297,7 @@ export class CustomModesManager { let settings try { - settings = JSON.parse(content) + settings = yaml.parse(content) } catch (error) { console.error(`[CustomModesManager] Failed to parse JSON from ${filePath}:`, error) settings = { customModes: [] } diff --git a/src/core/config/__tests__/CustomModesManager.test.ts b/src/core/config/__tests__/CustomModesManager.test.ts index 065f1478285..1f1fea18829 100644 --- a/src/core/config/__tests__/CustomModesManager.test.ts +++ b/src/core/config/__tests__/CustomModesManager.test.ts @@ -8,6 +8,7 @@ import { ModeConfig } from "../../../shared/modes" import { fileExistsAtPath } from "../../../utils/fs" import { getWorkspacePath, arePathsEqual } from "../../../utils/path" import { GlobalFileNames } from "../../../shared/globalFileNames" +import * as yaml from "yaml" jest.mock("vscode") jest.mock("fs/promises") @@ -60,6 +61,26 @@ describe("CustomModesManager", () => { }) describe("getCustomModes", () => { + it("should handle valid YAML in .roomodes file and JSON for global customModes", async () => { + const settingsModes = [{ slug: "mode1", name: "Mode 1", roleDefinition: "Role 1", groups: ["read"] }] + + const roomodesModes = [{ slug: "mode2", name: "Mode 2", roleDefinition: "Role 2", groups: ["read"] }] + + ;(fs.readFile as jest.Mock).mockImplementation(async (path: string) => { + if (path === mockSettingsPath) { + return JSON.stringify({ customModes: settingsModes }) + } + if (path === mockRoomodes) { + return yaml.stringify({ customModes: roomodesModes }) + } + throw new Error("File not found") + }) + + const modes = await manager.getCustomModes() + + expect(modes).toHaveLength(2) + }) + it("should merge modes with .roomodes taking precedence", async () => { const settingsModes = [ { slug: "mode1", name: "Mode 1", roleDefinition: "Role 1", groups: ["read"] }, @@ -423,10 +444,10 @@ describe("CustomModesManager", () => { ;(fs.writeFile as jest.Mock).mockImplementation( async (path: string, content: string, _encoding?: string) => { if (path === mockSettingsPath) { - settingsContent = JSON.parse(content) + settingsContent = yaml.parse(content) } if (path === mockRoomodes) { - roomodesContent = JSON.parse(content) + roomodesContent = yaml.parse(content) } return Promise.resolve() }, @@ -439,7 +460,7 @@ describe("CustomModesManager", () => { // Verify the content of the write const writeCall = (fs.writeFile as jest.Mock).mock.calls[0] - const content = JSON.parse(writeCall[1]) + const content = yaml.parse(writeCall[1]) expect(content.customModes).toContainEqual( expect.objectContaining({ slug: "mode1", @@ -493,7 +514,7 @@ describe("CustomModesManager", () => { }) ;(fs.writeFile as jest.Mock).mockImplementation(async (path: string, content: string) => { if (path === mockRoomodes) { - roomodesContent = JSON.parse(content) + roomodesContent = yaml.parse(content) } return Promise.resolve() }) @@ -550,7 +571,7 @@ describe("CustomModesManager", () => { ;(fs.writeFile as jest.Mock).mockImplementation( async (path: string, content: string, _encoding?: string) => { if (path === mockSettingsPath) { - settingsContent = JSON.parse(content) + settingsContent = yaml.parse(content) } return Promise.resolve() }, @@ -659,7 +680,7 @@ describe("CustomModesManager", () => { ;(fs.writeFile as jest.Mock).mockImplementation( async (path: string, content: string, encoding?: string) => { if (path === mockSettingsPath && encoding === "utf-8") { - settingsContent = JSON.parse(content) + settingsContent = yaml.parse(content) } return Promise.resolve() }, @@ -713,7 +734,7 @@ describe("CustomModesManager", () => { // Verify that a valid JSON structure was written const writeCall = (fs.writeFile as jest.Mock).mock.calls[0] - const writtenContent = JSON.parse(writeCall[1]) + const writtenContent = yaml.parse(writeCall[1]) expect(writtenContent).toEqual({ customModes: [ expect.objectContaining({