Browse Source

Add YAML support for .roomode files alongside JSON processing (#3711)

R-omk 7 months ago
parent
commit
527050eeb0

+ 5 - 5
package-lock.json

@@ -65,6 +65,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": {
@@ -20723,16 +20724,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": {

+ 1 - 0
package.json

@@ -421,6 +421,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": {

+ 4 - 3
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<ModeConfig[]> {
 		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: [] }

+ 28 - 7
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({