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

This commit is contained in:
R-omk 2025-05-20 16:59:23 +03:00 committed by GitHub
parent 6a6df08fbd
commit 527050eeb0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 38 additions and 15 deletions

10
package-lock.json generated
View file

@ -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": {

View file

@ -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": {

View file

@ -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: [] }

View file

@ -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({