mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-10 22:41:14 +00:00
fix: handle YAML parsing edge cases in CustomModesManager (#5099)
* fix: handle YAML parsing edge cases in CustomModesManager - Add BOM (Byte Order Mark) stripping for UTF-8 and UTF-16 - Normalize invisible characters including non-breaking spaces - Replace fancy quotes and dashes with standard characters - Remove zero-width characters that can cause parsing issues - Add comprehensive test coverage for all edge cases This fixes the YAML parsing limitations documented in PR #237 by implementing proper preprocessing before parsing YAML content. * fix: address PR review comments - Fix BOM handling to correctly handle UTF-16 (all BOMs appear as \uFEFF when decoded) - Optimize cleanInvisibleCharacters with single regex pass for better performance - Prevent duplicate error messages by marking errors as already handled - Refactor test file to use mockFsReadFile helper function to reduce duplication - Fix YAML indentation in tests (use spaces instead of tabs) - Add ESLint disable comment for character class warning (regex is correct) * fix: prevent YAML line breaks by setting lineWidth to 0 - Added lineWidth: 0 option to all yaml.stringify() calls - Prevents automatic line wrapping at 80 characters - Improves readability of YAML output for long strings - Applied to CustomModesManager, SimpleInstaller, and migrateSettings * fix: add defaultStringType option to yaml.stringify calls - Added defaultStringType: 'PLAIN' to minimize formatting changes - This helps preserve plain scalars when possible - Works alongside lineWidth: 0 to prevent automatic line wrapping * refactor: extract problematic characters regex as a named constant - Move regex pattern to PROBLEMATIC_CHARS_REGEX static constant - Add comprehensive documentation for each character range - Improves maintainability and makes the pattern reusable * test: add comprehensive edge case tests for YAML parsing - Add test for mixed line endings (CRLF vs LF) - Add test for multiple BOMs in sequence - Add test for deeply nested structures with problematic characters - Ensures robustness across different real-world scenarios * feat(i18n): add error messages for custom modes in multiple languages * fix: update tests to expect i18n keys instead of hardcoded strings - Update CustomModesManager tests to expect translation keys - Fix YAML edge case tests to match new i18n error messages - All tests now pass with the i18n integration * refactor: use strip-bom package and fix error handling - Replace custom stripBOM method with existing strip-bom package - Fix duplicate error handling in parseYamlSafely by returning empty object instead of re-throwing - Addresses review comments from PR #5099 --------- Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
This commit is contained in:
parent
896e9867ce
commit
e559beee67
23 changed files with 806 additions and 26 deletions
|
|
@ -3,6 +3,7 @@ import * as path from "path"
|
|||
import * as fs from "fs/promises"
|
||||
|
||||
import * as yaml from "yaml"
|
||||
import stripBom from "strip-bom"
|
||||
|
||||
import { type ModeConfig, customModesSettingsSchema } from "@roo-code/types"
|
||||
|
||||
|
|
@ -11,6 +12,7 @@ import { getWorkspacePath } from "../../utils/path"
|
|||
import { logger } from "../../utils/logging"
|
||||
import { GlobalFileNames } from "../../shared/globalFileNames"
|
||||
import { ensureSettingsDirectoryExists } from "../../utils/globalContext"
|
||||
import { t } from "../../i18n"
|
||||
|
||||
const ROOMODES_FILENAME = ".roomodes"
|
||||
|
||||
|
|
@ -73,12 +75,88 @@ export class CustomModesManager {
|
|||
return exists ? roomodesPath : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex pattern for problematic characters that need to be cleaned from YAML content
|
||||
* Includes:
|
||||
* - \u00A0: Non-breaking space
|
||||
* - \u200B-\u200D: Zero-width spaces and joiners
|
||||
* - \u2010-\u2015, \u2212: Various dash characters
|
||||
* - \u2018-\u2019: Smart single quotes
|
||||
* - \u201C-\u201D: Smart double quotes
|
||||
*/
|
||||
private static readonly PROBLEMATIC_CHARS_REGEX =
|
||||
// eslint-disable-next-line no-misleading-character-class
|
||||
/[\u00A0\u200B\u200C\u200D\u2010\u2011\u2012\u2013\u2014\u2015\u2212\u2018\u2019\u201C\u201D]/g
|
||||
|
||||
/**
|
||||
* Clean invisible and problematic characters from YAML content
|
||||
*/
|
||||
private cleanInvisibleCharacters(content: string): string {
|
||||
// Single pass replacement for all problematic characters
|
||||
return content.replace(CustomModesManager.PROBLEMATIC_CHARS_REGEX, (match) => {
|
||||
switch (match) {
|
||||
case "\u00A0": // Non-breaking space
|
||||
return " "
|
||||
case "\u200B": // Zero-width space
|
||||
case "\u200C": // Zero-width non-joiner
|
||||
case "\u200D": // Zero-width joiner
|
||||
return ""
|
||||
case "\u2018": // Left single quotation mark
|
||||
case "\u2019": // Right single quotation mark
|
||||
return "'"
|
||||
case "\u201C": // Left double quotation mark
|
||||
case "\u201D": // Right double quotation mark
|
||||
return '"'
|
||||
default: // Dash characters (U+2010 through U+2015, U+2212)
|
||||
return "-"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse YAML content with enhanced error handling and preprocessing
|
||||
*/
|
||||
private parseYamlSafely(content: string, filePath: string): any {
|
||||
// Clean the content
|
||||
let cleanedContent = stripBom(content)
|
||||
cleanedContent = this.cleanInvisibleCharacters(cleanedContent)
|
||||
|
||||
try {
|
||||
return yaml.parse(cleanedContent)
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||
console.error(`[CustomModesManager] Failed to parse YAML from ${filePath}:`, errorMsg)
|
||||
|
||||
// Show user-friendly error message for .roomodes files
|
||||
if (filePath.endsWith(ROOMODES_FILENAME)) {
|
||||
const lineMatch = errorMsg.match(/at line (\d+)/)
|
||||
const line = lineMatch ? lineMatch[1] : "unknown"
|
||||
vscode.window.showErrorMessage(t("common:customModes.errors.yamlParseError", { line }))
|
||||
}
|
||||
|
||||
// Return empty object to prevent duplicate error handling
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
private async loadModesFromFile(filePath: string): Promise<ModeConfig[]> {
|
||||
try {
|
||||
const content = await fs.readFile(filePath, "utf-8")
|
||||
const settings = yaml.parse(content)
|
||||
const settings = this.parseYamlSafely(content, filePath)
|
||||
const result = customModesSettingsSchema.safeParse(settings)
|
||||
|
||||
if (!result.success) {
|
||||
console.error(`[CustomModesManager] Schema validation failed for ${filePath}:`, result.error)
|
||||
|
||||
// Show user-friendly error for .roomodes files
|
||||
if (filePath.endsWith(ROOMODES_FILENAME)) {
|
||||
const issues = result.error.issues
|
||||
.map((issue) => `• ${issue.path.join(".")}: ${issue.message}`)
|
||||
.join("\n")
|
||||
|
||||
vscode.window.showErrorMessage(t("common:customModes.errors.schemaValidationError", { issues }))
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
|
|
@ -89,8 +167,11 @@ export class CustomModesManager {
|
|||
// Add source to each mode
|
||||
return result.data.customModes.map((mode) => ({ ...mode, source }))
|
||||
} catch (error) {
|
||||
const errorMsg = `Failed to load modes from ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
||||
console.error(`[CustomModesManager] ${errorMsg}`)
|
||||
// Only log if the error wasn't already handled in parseYamlSafely
|
||||
if (!(error as any).alreadyHandled) {
|
||||
const errorMsg = `Failed to load modes from ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
||||
console.error(`[CustomModesManager] ${errorMsg}`)
|
||||
}
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
|
@ -124,7 +205,12 @@ export class CustomModesManager {
|
|||
const fileExists = await fileExistsAtPath(filePath)
|
||||
|
||||
if (!fileExists) {
|
||||
await this.queueWrite(() => fs.writeFile(filePath, yaml.stringify({ customModes: [] })))
|
||||
await this.queueWrite(() =>
|
||||
fs.writeFile(
|
||||
filePath,
|
||||
yaml.stringify({ customModes: [] }, { lineWidth: 0, defaultStringType: "PLAIN" }),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return filePath
|
||||
|
|
@ -147,13 +233,12 @@ export class CustomModesManager {
|
|||
await this.getCustomModesFilePath()
|
||||
const content = await fs.readFile(settingsPath, "utf-8")
|
||||
|
||||
const errorMessage =
|
||||
"Invalid custom modes format. Please ensure your settings follow the correct YAML format."
|
||||
const errorMessage = t("common:customModes.errors.invalidFormat")
|
||||
|
||||
let config: any
|
||||
|
||||
try {
|
||||
config = yaml.parse(content)
|
||||
config = this.parseYamlSafely(content, settingsPath)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
vscode.window.showErrorMessage(errorMessage)
|
||||
|
|
@ -284,7 +369,7 @@ export class CustomModesManager {
|
|||
|
||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||
logger.error("Failed to update project mode: No workspace folder found", { slug })
|
||||
throw new Error("No workspace folder found for project-specific mode")
|
||||
throw new Error(t("common:customModes.errors.noWorkspaceForProject"))
|
||||
}
|
||||
|
||||
const workspaceRoot = getWorkspacePath()
|
||||
|
|
@ -318,7 +403,7 @@ export class CustomModesManager {
|
|||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
logger.error("Failed to update custom mode", { slug, error: errorMessage })
|
||||
vscode.window.showErrorMessage(`Failed to update custom mode: ${errorMessage}`)
|
||||
vscode.window.showErrorMessage(t("common:customModes.errors.updateFailed", { error: errorMessage }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -329,20 +414,20 @@ export class CustomModesManager {
|
|||
content = await fs.readFile(filePath, "utf-8")
|
||||
} catch (error) {
|
||||
// File might not exist yet.
|
||||
content = yaml.stringify({ customModes: [] })
|
||||
content = yaml.stringify({ customModes: [] }, { lineWidth: 0, defaultStringType: "PLAIN" })
|
||||
}
|
||||
|
||||
let settings
|
||||
|
||||
try {
|
||||
settings = yaml.parse(content)
|
||||
settings = this.parseYamlSafely(content, filePath)
|
||||
} catch (error) {
|
||||
console.error(`[CustomModesManager] Failed to parse YAML from ${filePath}:`, error)
|
||||
// Error already logged in parseYamlSafely
|
||||
settings = { customModes: [] }
|
||||
}
|
||||
|
||||
settings.customModes = operation(settings.customModes || [])
|
||||
await fs.writeFile(filePath, yaml.stringify(settings), "utf-8")
|
||||
await fs.writeFile(filePath, yaml.stringify(settings, { lineWidth: 0, defaultStringType: "PLAIN" }), "utf-8")
|
||||
}
|
||||
|
||||
private async refreshMergedState(): Promise<void> {
|
||||
|
|
@ -373,7 +458,7 @@ export class CustomModesManager {
|
|||
const globalMode = settingsModes.find((m) => m.slug === slug)
|
||||
|
||||
if (!projectMode && !globalMode) {
|
||||
throw new Error("Write error: Mode not found")
|
||||
throw new Error(t("common:customModes.errors.modeNotFound"))
|
||||
}
|
||||
|
||||
await this.queueWrite(async () => {
|
||||
|
|
@ -392,23 +477,24 @@ export class CustomModesManager {
|
|||
await this.refreshMergedState()
|
||||
})
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to delete custom mode: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
vscode.window.showErrorMessage(t("common:customModes.errors.deleteFailed", { error: errorMessage }))
|
||||
}
|
||||
}
|
||||
|
||||
public async resetCustomModes(): Promise<void> {
|
||||
try {
|
||||
const filePath = await this.getCustomModesFilePath()
|
||||
await fs.writeFile(filePath, yaml.stringify({ customModes: [] }))
|
||||
await fs.writeFile(
|
||||
filePath,
|
||||
yaml.stringify({ customModes: [] }, { lineWidth: 0, defaultStringType: "PLAIN" }),
|
||||
)
|
||||
await this.context.globalState.update("customModes", [])
|
||||
this.clearCache()
|
||||
await this.onUpdate()
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to reset custom modes: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
vscode.window.showErrorMessage(t("common:customModes.errors.resetFailed", { error: errorMessage }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -754,7 +754,7 @@ describe("CustomModesManager", () => {
|
|||
|
||||
await manager.deleteCustomMode("non-existent-mode")
|
||||
|
||||
expect(mockShowError).toHaveBeenCalledWith(expect.stringContaining("Write error"))
|
||||
expect(mockShowError).toHaveBeenCalledWith("customModes.errors.deleteFailed")
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,474 @@
|
|||
// npx vitest core/config/__tests__/CustomModesManager.yamlEdgeCases.spec.ts
|
||||
|
||||
import type { Mock } from "vitest"
|
||||
|
||||
import * as path from "path"
|
||||
import * as fs from "fs/promises"
|
||||
|
||||
import * as yaml from "yaml"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { ModeConfig } from "@roo-code/types"
|
||||
|
||||
import { fileExistsAtPath } from "../../../utils/fs"
|
||||
import { getWorkspacePath } from "../../../utils/path"
|
||||
import { GlobalFileNames } from "../../../shared/globalFileNames"
|
||||
|
||||
import { CustomModesManager } from "../CustomModesManager"
|
||||
|
||||
vi.mock("vscode", () => ({
|
||||
workspace: {
|
||||
workspaceFolders: [],
|
||||
onDidSaveTextDocument: vi.fn(),
|
||||
createFileSystemWatcher: vi.fn(),
|
||||
},
|
||||
window: {
|
||||
showErrorMessage: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("fs/promises")
|
||||
|
||||
vi.mock("../../../utils/fs")
|
||||
vi.mock("../../../utils/path")
|
||||
|
||||
describe("CustomModesManager - YAML Edge Cases", () => {
|
||||
let manager: CustomModesManager
|
||||
let mockContext: vscode.ExtensionContext
|
||||
let mockOnUpdate: Mock
|
||||
let mockWorkspaceFolders: { uri: { fsPath: string } }[]
|
||||
|
||||
const mockStoragePath = `${path.sep}mock${path.sep}settings`
|
||||
const mockSettingsPath = path.join(mockStoragePath, "settings", GlobalFileNames.customModes)
|
||||
const mockRoomodes = `${path.sep}mock${path.sep}workspace${path.sep}.roomodes`
|
||||
|
||||
// Helper function to reduce duplication in fs.readFile mocks
|
||||
const mockFsReadFile = (files: Record<string, string>) => {
|
||||
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
|
||||
if (files[path]) return files[path]
|
||||
throw new Error("File not found")
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockOnUpdate = vi.fn()
|
||||
mockContext = {
|
||||
globalState: {
|
||||
get: vi.fn(),
|
||||
update: vi.fn(),
|
||||
keys: vi.fn(() => []),
|
||||
setKeysForSync: vi.fn(),
|
||||
},
|
||||
globalStorageUri: {
|
||||
fsPath: mockStoragePath,
|
||||
},
|
||||
} as unknown as vscode.ExtensionContext
|
||||
|
||||
mockWorkspaceFolders = [{ uri: { fsPath: "/mock/workspace" } }]
|
||||
;(vscode.workspace as any).workspaceFolders = mockWorkspaceFolders
|
||||
;(vscode.workspace.onDidSaveTextDocument as Mock).mockReturnValue({ dispose: vi.fn() })
|
||||
;(getWorkspacePath as Mock).mockReturnValue("/mock/workspace")
|
||||
;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
|
||||
return path === mockSettingsPath || path === mockRoomodes
|
||||
})
|
||||
;(fs.mkdir as Mock).mockResolvedValue(undefined)
|
||||
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
|
||||
if (path === mockSettingsPath) {
|
||||
return yaml.stringify({ customModes: [] })
|
||||
}
|
||||
throw new Error("File not found")
|
||||
})
|
||||
|
||||
// Mock createFileSystemWatcher to prevent file watching in tests
|
||||
const mockWatcher = {
|
||||
onDidChange: vi.fn().mockReturnValue({ dispose: vi.fn() }),
|
||||
onDidCreate: vi.fn().mockReturnValue({ dispose: vi.fn() }),
|
||||
onDidDelete: vi.fn().mockReturnValue({ dispose: vi.fn() }),
|
||||
dispose: vi.fn(),
|
||||
}
|
||||
;(vscode.workspace.createFileSystemWatcher as Mock).mockReturnValue(mockWatcher)
|
||||
|
||||
manager = new CustomModesManager(mockContext, mockOnUpdate)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("BOM (Byte Order Mark) handling", () => {
|
||||
it("should handle UTF-8 BOM in YAML files", async () => {
|
||||
const yamlWithBOM =
|
||||
"\uFEFF" +
|
||||
yaml.stringify({
|
||||
customModes: [
|
||||
{
|
||||
slug: "test-mode",
|
||||
name: "Test Mode",
|
||||
roleDefinition: "Test role",
|
||||
groups: ["read"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: yamlWithBOM,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
expect(modes).toHaveLength(1)
|
||||
expect(modes[0].slug).toBe("test-mode")
|
||||
expect(modes[0].name).toBe("Test Mode")
|
||||
})
|
||||
|
||||
it("should handle UTF-16 BOM in YAML files", async () => {
|
||||
// When Node.js reads UTF-16 files, the BOM is correctly decoded as \uFEFF
|
||||
const yamlWithBOM =
|
||||
"\uFEFF" +
|
||||
yaml.stringify({
|
||||
customModes: [
|
||||
{
|
||||
slug: "utf16-mode",
|
||||
name: "UTF-16 Mode",
|
||||
roleDefinition: "Test role",
|
||||
groups: ["read"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: yamlWithBOM,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
expect(modes).toHaveLength(1)
|
||||
expect(modes[0].slug).toBe("utf16-mode")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Invisible character handling", () => {
|
||||
it("should handle non-breaking spaces in YAML", async () => {
|
||||
// YAML with non-breaking spaces (U+00A0) instead of regular spaces
|
||||
const yamlWithNonBreakingSpaces = `customModes:
|
||||
- slug: "test-mode"
|
||||
name: "Test\u00A0Mode"
|
||||
roleDefinition: "Test\u00A0role\u00A0with\u00A0non-breaking\u00A0spaces"
|
||||
groups: ["read"]`
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: yamlWithNonBreakingSpaces,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
expect(modes).toHaveLength(1)
|
||||
expect(modes[0].name).toBe("Test Mode") // Non-breaking spaces replaced with regular spaces
|
||||
expect(modes[0].roleDefinition).toBe("Test role with non-breaking spaces")
|
||||
})
|
||||
|
||||
it("should handle zero-width characters", async () => {
|
||||
// YAML with zero-width characters
|
||||
const yamlWithZeroWidth = `customModes:
|
||||
- slug: "test-mode"
|
||||
name: "Test\u200BMode\u200C"
|
||||
roleDefinition: "Test\u200Drole"
|
||||
groups: ["read"]`
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: yamlWithZeroWidth,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
expect(modes).toHaveLength(1)
|
||||
expect(modes[0].name).toBe("TestMode") // Zero-width characters removed
|
||||
expect(modes[0].roleDefinition).toBe("Testrole")
|
||||
})
|
||||
|
||||
it("should normalize various quote characters", async () => {
|
||||
// Use fancy quotes that will be normalized before YAML parsing
|
||||
// The fancy quotes will be normalized to standard quotes
|
||||
const yamlWithFancyQuotes = yaml.stringify({
|
||||
customModes: [
|
||||
{
|
||||
slug: "test-mode",
|
||||
name: "Test Mode",
|
||||
roleDefinition: "Test role with \u2018fancy\u2019 quotes and \u201Ccurly\u201D quotes",
|
||||
groups: ["read"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: yamlWithFancyQuotes,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
expect(modes).toHaveLength(1)
|
||||
expect(modes[0].roleDefinition).toBe("Test role with 'fancy' quotes and \"curly\" quotes")
|
||||
})
|
||||
})
|
||||
|
||||
// Note: YAML anchor/alias support has been removed to reduce complexity
|
||||
// If needed in the future, users should pre-process their YAML files
|
||||
|
||||
describe("Complex fileRegex handling", () => {
|
||||
it("should handle complex fileRegex syntax gracefully", async () => {
|
||||
const yamlWithComplexFileRegex = yaml.stringify({
|
||||
customModes: [
|
||||
{
|
||||
slug: "test-mode",
|
||||
name: "Test Mode",
|
||||
roleDefinition: "Test role",
|
||||
groups: [
|
||||
"read",
|
||||
["edit", { fileRegex: "\\.md$", description: "Markdown files only" }],
|
||||
"browser",
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: yamlWithComplexFileRegex,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
// Should successfully parse the complex fileRegex syntax
|
||||
expect(modes).toHaveLength(1)
|
||||
expect(modes[0].groups).toHaveLength(3)
|
||||
expect(modes[0].groups[1]).toEqual(["edit", { fileRegex: "\\.md$", description: "Markdown files only" }])
|
||||
})
|
||||
|
||||
it("should handle invalid fileRegex syntax with clear error", async () => {
|
||||
// This YAML has invalid structure that might cause parsing issues
|
||||
const invalidYaml = `customModes:
|
||||
- slug: "test-mode"
|
||||
name: "Test Mode"
|
||||
roleDefinition: "Test role"
|
||||
groups:
|
||||
- read
|
||||
- ["edit", { fileRegex: "\\.md$" }] # This line has invalid YAML syntax
|
||||
- browser`
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: invalidYaml,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
// Should handle the error gracefully
|
||||
expect(modes).toHaveLength(0)
|
||||
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("customModes.errors.yamlParseError")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Error messages", () => {
|
||||
it("should provide detailed syntax error messages with context", async () => {
|
||||
const invalidYaml = `customModes:
|
||||
- slug: "test-mode"
|
||||
name: "Test Mode"
|
||||
roleDefinition: "Test role
|
||||
groups: ["read"]` // Missing closing quote
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: invalidYaml,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
// Should fallback to empty array and show detailed error
|
||||
expect(modes).toHaveLength(0)
|
||||
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("customModes.errors.yamlParseError")
|
||||
})
|
||||
|
||||
it("should provide schema validation error messages", async () => {
|
||||
const invalidSchema = yaml.stringify({
|
||||
customModes: [
|
||||
{
|
||||
slug: "test-mode",
|
||||
name: "Test Mode",
|
||||
// Missing required 'roleDefinition' field
|
||||
groups: ["read"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: invalidSchema,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
// Should show schema validation error
|
||||
expect(modes).toHaveLength(0)
|
||||
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("customModes.errors.schemaValidationError")
|
||||
})
|
||||
})
|
||||
|
||||
describe("UTF-8 encoding", () => {
|
||||
it("should handle special characters and emojis", async () => {
|
||||
const yamlWithEmojis = yaml.stringify({
|
||||
customModes: [
|
||||
{
|
||||
slug: "emoji-mode",
|
||||
name: "📝 Writing Mode",
|
||||
roleDefinition: "A mode for writing with emojis 🚀",
|
||||
groups: ["read", "edit"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: yamlWithEmojis,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
expect(modes).toHaveLength(1)
|
||||
expect(modes[0].name).toBe("📝 Writing Mode")
|
||||
expect(modes[0].roleDefinition).toBe("A mode for writing with emojis 🚀")
|
||||
})
|
||||
|
||||
it("should handle various international characters", async () => {
|
||||
const yamlWithInternational = yaml.stringify({
|
||||
customModes: [
|
||||
{
|
||||
slug: "intl-mode",
|
||||
name: "Mode Français",
|
||||
roleDefinition: "Mode für Deutsch, 日本語モード, Режим русский",
|
||||
groups: ["read"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: yamlWithInternational,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
expect(modes).toHaveLength(1)
|
||||
expect(modes[0].roleDefinition).toContain("für Deutsch")
|
||||
expect(modes[0].roleDefinition).toContain("日本語モード")
|
||||
expect(modes[0].roleDefinition).toContain("Режим русский")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Additional edge cases", () => {
|
||||
it("should handle mixed line endings (CRLF vs LF)", async () => {
|
||||
// YAML with mixed line endings
|
||||
const yamlWithMixedLineEndings =
|
||||
"customModes:\r\n" +
|
||||
' - slug: "test-mode"\n' +
|
||||
' name: "Test Mode"\r\n' +
|
||||
' roleDefinition: "Test role with mixed line endings"\n' +
|
||||
' groups: ["read"]'
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: yamlWithMixedLineEndings,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
expect(modes).toHaveLength(1)
|
||||
expect(modes[0].slug).toBe("test-mode")
|
||||
expect(modes[0].roleDefinition).toBe("Test role with mixed line endings")
|
||||
})
|
||||
|
||||
it("should handle multiple BOMs in sequence", async () => {
|
||||
// File with multiple BOMs (edge case from file concatenation)
|
||||
const yamlWithMultipleBOMs =
|
||||
"\uFEFF\uFEFF" +
|
||||
yaml.stringify({
|
||||
customModes: [
|
||||
{
|
||||
slug: "multi-bom-mode",
|
||||
name: "Multi BOM Mode",
|
||||
roleDefinition: "Test role",
|
||||
groups: ["read"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: yamlWithMultipleBOMs,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
expect(modes).toHaveLength(1)
|
||||
expect(modes[0].slug).toBe("multi-bom-mode")
|
||||
})
|
||||
|
||||
it("should handle deeply nested structures with edge case characters", async () => {
|
||||
const yamlWithComplexNesting = yaml.stringify({
|
||||
customModes: [
|
||||
{
|
||||
slug: "complex-mode",
|
||||
name: "Complex\u00A0Mode\u2019s Name",
|
||||
roleDefinition: "Complex role with \u201Cquotes\u201D and \u2014dashes\u2014",
|
||||
groups: [
|
||||
"read",
|
||||
[
|
||||
"edit",
|
||||
{
|
||||
fileRegex: "\\.md$",
|
||||
description: "Markdown files with \u2018special\u2019 chars",
|
||||
},
|
||||
],
|
||||
[
|
||||
"browser",
|
||||
{
|
||||
fileRegex: "\\.html?$",
|
||||
description: "HTML files\u00A0only",
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
mockFsReadFile({
|
||||
[mockRoomodes]: yamlWithComplexNesting,
|
||||
[mockSettingsPath]: yaml.stringify({ customModes: [] }),
|
||||
})
|
||||
|
||||
const modes = await manager.getCustomModes()
|
||||
|
||||
expect(modes).toHaveLength(1)
|
||||
expect(modes[0].name).toBe("Complex Mode's Name")
|
||||
expect(modes[0].roleDefinition).toBe('Complex role with "quotes" and -dashes-')
|
||||
expect(modes[0].groups[1]).toEqual([
|
||||
"edit",
|
||||
{
|
||||
fileRegex: "\\.md$",
|
||||
description: "Markdown files with 'special' chars",
|
||||
},
|
||||
])
|
||||
expect(modes[0].groups[2]).toEqual([
|
||||
"browser",
|
||||
{
|
||||
fileRegex: "\\.html?$",
|
||||
description: "HTML files only",
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": "YAML no vàlid al fitxer .roomodes a la línia {{line}}. Comprova:\n• Indentació correcta (utilitza espais, no tabuladors)\n• Cometes i claudàtors coincidents\n• Sintaxi YAML vàlida",
|
||||
"schemaValidationError": "Format de modes personalitzats no vàlid a .roomodes:\n{{issues}}",
|
||||
"invalidFormat": "Format de modes personalitzats no vàlid. Assegura't que la teva configuració segueix el format YAML correcte.",
|
||||
"updateFailed": "Error en actualitzar el mode personalitzat: {{error}}",
|
||||
"deleteFailed": "Error en eliminar el mode personalitzat: {{error}}",
|
||||
"resetFailed": "Error en restablir els modes personalitzats: {{error}}",
|
||||
"modeNotFound": "Error d'escriptura: Mode no trobat",
|
||||
"noWorkspaceForProject": "No s'ha trobat cap carpeta d'espai de treball per al mode específic del projecte"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "La teva organització requereix autenticació de Roo Code Cloud. Si us plau, inicia sessió per continuar.",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": "Ungültiges YAML in .roomodes-Datei in Zeile {{line}}. Bitte überprüfe:\n• Korrekte Einrückung (verwende Leerzeichen, keine Tabs)\n• Passende Anführungszeichen und Klammern\n• Gültige YAML-Syntax",
|
||||
"schemaValidationError": "Ungültiges Format für benutzerdefinierte Modi in .roomodes:\n{{issues}}",
|
||||
"invalidFormat": "Ungültiges Format für benutzerdefinierte Modi. Bitte stelle sicher, dass deine Einstellungen dem korrekten YAML-Format folgen.",
|
||||
"updateFailed": "Fehler beim Aktualisieren des benutzerdefinierten Modus: {{error}}",
|
||||
"deleteFailed": "Fehler beim Löschen des benutzerdefinierten Modus: {{error}}",
|
||||
"resetFailed": "Fehler beim Zurücksetzen der benutzerdefinierten Modi: {{error}}",
|
||||
"modeNotFound": "Schreibfehler: Modus nicht gefunden",
|
||||
"noWorkspaceForProject": "Kein Arbeitsbereich-Ordner für projektspezifischen Modus gefunden"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Deine Organisation erfordert eine Roo Code Cloud-Authentifizierung. Bitte melde dich an, um fortzufahren.",
|
||||
|
|
|
|||
|
|
@ -111,6 +111,18 @@
|
|||
"task_prompt": "What should Roo do?",
|
||||
"task_placeholder": "Type your task here"
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": "Invalid YAML in .roomodes file at line {{line}}. Please check for:\n• Proper indentation (use spaces, not tabs)\n• Matching quotes and brackets\n• Valid YAML syntax",
|
||||
"schemaValidationError": "Invalid custom modes format in .roomodes:\n{{issues}}",
|
||||
"invalidFormat": "Invalid custom modes format. Please ensure your settings follow the correct YAML format.",
|
||||
"updateFailed": "Failed to update custom mode: {{error}}",
|
||||
"deleteFailed": "Failed to delete custom mode: {{error}}",
|
||||
"resetFailed": "Failed to reset custom modes: {{error}}",
|
||||
"modeNotFound": "Write error: Mode not found",
|
||||
"noWorkspaceForProject": "No workspace folder found for project-specific mode"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Your organization requires Roo Code Cloud authentication. Please sign in to continue.",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": "YAML inválido en archivo .roomodes en línea {{line}}. Verifica:\n• Indentación correcta (usa espacios, no tabs)\n• Comillas y corchetes coincidentes\n• Sintaxis YAML válida",
|
||||
"schemaValidationError": "Formato inválido de modos personalizados en .roomodes:\n{{issues}}",
|
||||
"invalidFormat": "Formato inválido de modos personalizados. Asegúrate de que tu configuración siga el formato YAML correcto.",
|
||||
"updateFailed": "Error al actualizar modo personalizado: {{error}}",
|
||||
"deleteFailed": "Error al eliminar modo personalizado: {{error}}",
|
||||
"resetFailed": "Error al restablecer modos personalizados: {{error}}",
|
||||
"modeNotFound": "Error de escritura: Modo no encontrado",
|
||||
"noWorkspaceForProject": "No se encontró carpeta de espacio de trabajo para modo específico del proyecto"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Tu organización requiere autenticación de Roo Code Cloud. Por favor, inicia sesión para continuar.",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": "YAML invalide dans le fichier .roomodes à la ligne {{line}}. Vérifie :\n• L'indentation correcte (utilise des espaces, pas de tabulations)\n• Les guillemets et crochets correspondants\n• La syntaxe YAML valide",
|
||||
"schemaValidationError": "Format invalide des modes personnalisés dans .roomodes :\n{{issues}}",
|
||||
"invalidFormat": "Format invalide des modes personnalisés. Assure-toi que tes paramètres suivent le format YAML correct.",
|
||||
"updateFailed": "Échec de la mise à jour du mode personnalisé : {{error}}",
|
||||
"deleteFailed": "Échec de la suppression du mode personnalisé : {{error}}",
|
||||
"resetFailed": "Échec de la réinitialisation des modes personnalisés : {{error}}",
|
||||
"modeNotFound": "Erreur d'écriture : Mode non trouvé",
|
||||
"noWorkspaceForProject": "Aucun dossier d'espace de travail trouvé pour le mode spécifique au projet"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Votre organisation nécessite une authentification Roo Code Cloud. Veuillez vous connecter pour continuer.",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": ".roomodes फ़ाइल में लाइन {{line}} पर अमान्य YAML। कृपया जांचें:\n• सही इंडेंटेशन (टैब नहीं, स्पेस का उपयोग करें)\n• मैचिंग कोट्स और ब्रैकेट्स\n• वैध YAML सिंटैक्स",
|
||||
"schemaValidationError": ".roomodes में अमान्य कस्टम मोड फॉर्मेट:\n{{issues}}",
|
||||
"invalidFormat": "अमान्य कस्टम मोड फॉर्मेट। कृपया सुनिश्चित करें कि आपकी सेटिंग्स सही YAML फॉर्मेट का पालन करती हैं।",
|
||||
"updateFailed": "कस्टम मोड अपडेट विफल: {{error}}",
|
||||
"deleteFailed": "कस्टम मोड डिलीट विफल: {{error}}",
|
||||
"resetFailed": "कस्टम मोड रीसेट विफल: {{error}}",
|
||||
"modeNotFound": "लेखन त्रुटि: मोड नहीं मिला",
|
||||
"noWorkspaceForProject": "प्रोजेक्ट-विशिष्ट मोड के लिए वर्कस्पेस फ़ोल्डर नहीं मिला"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "आपके संगठन को Roo Code Cloud प्रमाणीकरण की आवश्यकता है। कृपया जारी रखने के लिए साइन इन करें।",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": "YAML tidak valid dalam file .roomodes pada baris {{line}}. Silakan periksa:\n• Indentasi yang benar (gunakan spasi, bukan tab)\n• Tanda kutip dan kurung yang cocok\n• Sintaks YAML yang valid",
|
||||
"schemaValidationError": "Format mode kustom tidak valid dalam .roomodes:\n{{issues}}",
|
||||
"invalidFormat": "Format mode kustom tidak valid. Pastikan pengaturan kamu mengikuti format YAML yang benar.",
|
||||
"updateFailed": "Gagal memperbarui mode kustom: {{error}}",
|
||||
"deleteFailed": "Gagal menghapus mode kustom: {{error}}",
|
||||
"resetFailed": "Gagal mereset mode kustom: {{error}}",
|
||||
"modeNotFound": "Kesalahan tulis: Mode tidak ditemukan",
|
||||
"noWorkspaceForProject": "Tidak ditemukan folder workspace untuk mode khusus proyek"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Organisasi kamu memerlukan autentikasi Roo Code Cloud. Silakan masuk untuk melanjutkan.",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": "YAML non valido nel file .roomodes alla riga {{line}}. Controlla:\n• Indentazione corretta (usa spazi, non tab)\n• Virgolette e parentesi corrispondenti\n• Sintassi YAML valida",
|
||||
"schemaValidationError": "Formato modalità personalizzate non valido in .roomodes:\n{{issues}}",
|
||||
"invalidFormat": "Formato modalità personalizzate non valido. Assicurati che le tue impostazioni seguano il formato YAML corretto.",
|
||||
"updateFailed": "Aggiornamento modalità personalizzata fallito: {{error}}",
|
||||
"deleteFailed": "Eliminazione modalità personalizzata fallita: {{error}}",
|
||||
"resetFailed": "Reset modalità personalizzate fallito: {{error}}",
|
||||
"modeNotFound": "Errore di scrittura: Modalità non trovata",
|
||||
"noWorkspaceForProject": "Nessuna cartella workspace trovata per la modalità specifica del progetto"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "La tua organizzazione richiede l'autenticazione Roo Code Cloud. Accedi per continuare.",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": ".roomodes ファイルの {{line}} 行目で無効な YAML です。以下を確認してください:\n• 正しいインデント(タブではなくスペースを使用)\n• 引用符と括弧の対応\n• 有効な YAML 構文",
|
||||
"schemaValidationError": ".roomodes のカスタムモード形式が無効です:\n{{issues}}",
|
||||
"invalidFormat": "カスタムモード形式が無効です。設定が正しい YAML 形式に従っていることを確認してください。",
|
||||
"updateFailed": "カスタムモードの更新に失敗しました:{{error}}",
|
||||
"deleteFailed": "カスタムモードの削除に失敗しました:{{error}}",
|
||||
"resetFailed": "カスタムモードのリセットに失敗しました:{{error}}",
|
||||
"modeNotFound": "書き込みエラー:モードが見つかりません",
|
||||
"noWorkspaceForProject": "プロジェクト固有モード用のワークスペースフォルダーが見つかりません"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "あなたの組織では Roo Code Cloud 認証が必要です。続行するにはサインインしてください。",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": ".roomodes 파일의 {{line}}번째 줄에서 유효하지 않은 YAML입니다. 다음을 확인하세요:\n• 올바른 들여쓰기 (탭이 아닌 공백 사용)\n• 일치하는 따옴표와 괄호\n• 유효한 YAML 구문",
|
||||
"schemaValidationError": ".roomodes의 사용자 정의 모드 형식이 유효하지 않습니다:\n{{issues}}",
|
||||
"invalidFormat": "사용자 정의 모드 형식이 유효하지 않습니다. 설정이 올바른 YAML 형식을 따르는지 확인하세요.",
|
||||
"updateFailed": "사용자 정의 모드 업데이트 실패: {{error}}",
|
||||
"deleteFailed": "사용자 정의 모드 삭제 실패: {{error}}",
|
||||
"resetFailed": "사용자 정의 모드 재설정 실패: {{error}}",
|
||||
"modeNotFound": "쓰기 오류: 모드를 찾을 수 없습니다",
|
||||
"noWorkspaceForProject": "프로젝트별 모드용 작업 공간 폴더를 찾을 수 없습니다"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "조직에서 Roo Code Cloud 인증이 필요합니다. 계속하려면 로그인하세요.",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": "Ongeldige YAML in .roomodes bestand op regel {{line}}. Controleer:\n• Juiste inspringing (gebruik spaties, geen tabs)\n• Overeenkomende aanhalingstekens en haakjes\n• Geldige YAML syntaxis",
|
||||
"schemaValidationError": "Ongeldig aangepaste modi formaat in .roomodes:\n{{issues}}",
|
||||
"invalidFormat": "Ongeldig aangepaste modi formaat. Zorg ervoor dat je instellingen het juiste YAML formaat volgen.",
|
||||
"updateFailed": "Aangepaste modus bijwerken mislukt: {{error}}",
|
||||
"deleteFailed": "Aangepaste modus verwijderen mislukt: {{error}}",
|
||||
"resetFailed": "Aangepaste modi resetten mislukt: {{error}}",
|
||||
"modeNotFound": "Schrijffout: Modus niet gevonden",
|
||||
"noWorkspaceForProject": "Geen workspace map gevonden voor projectspecifieke modus"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Je organisatie vereist Roo Code Cloud-authenticatie. Log in om door te gaan.",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": "Nieprawidłowy YAML w pliku .roomodes w linii {{line}}. Sprawdź:\n• Prawidłowe wcięcia (używaj spacji, nie tabulatorów)\n• Pasujące cudzysłowy i nawiasy\n• Prawidłową składnię YAML",
|
||||
"schemaValidationError": "Nieprawidłowy format trybów niestandardowych w .roomodes:\n{{issues}}",
|
||||
"invalidFormat": "Nieprawidłowy format trybów niestandardowych. Upewnij się, że twoje ustawienia są zgodne z prawidłowym formatem YAML.",
|
||||
"updateFailed": "Aktualizacja trybu niestandardowego nie powiodła się: {{error}}",
|
||||
"deleteFailed": "Usunięcie trybu niestandardowego nie powiodło się: {{error}}",
|
||||
"resetFailed": "Resetowanie trybów niestandardowych nie powiodło się: {{error}}",
|
||||
"modeNotFound": "Błąd zapisu: Tryb nie został znaleziony",
|
||||
"noWorkspaceForProject": "Nie znaleziono folderu obszaru roboczego dla trybu specyficznego dla projektu"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Twoja organizacja wymaga uwierzytelnienia Roo Code Cloud. Zaloguj się, aby kontynuować.",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": "YAML inválido no arquivo .roomodes na linha {{line}}. Verifique:\n• Indentação correta (use espaços, não tabs)\n• Aspas e colchetes correspondentes\n• Sintaxe YAML válida",
|
||||
"schemaValidationError": "Formato de modos personalizados inválido em .roomodes:\n{{issues}}",
|
||||
"invalidFormat": "Formato de modos personalizados inválido. Certifique-se de que suas configurações seguem o formato YAML correto.",
|
||||
"updateFailed": "Falha ao atualizar modo personalizado: {{error}}",
|
||||
"deleteFailed": "Falha ao excluir modo personalizado: {{error}}",
|
||||
"resetFailed": "Falha ao redefinir modos personalizados: {{error}}",
|
||||
"modeNotFound": "Erro de escrita: Modo não encontrado",
|
||||
"noWorkspaceForProject": "Nenhuma pasta de workspace encontrada para modo específico do projeto"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Sua organização requer autenticação do Roo Code Cloud. Faça login para continuar.",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": "Недопустимый YAML в файле .roomodes на строке {{line}}. Проверь:\n• Правильные отступы (используй пробелы, не табы)\n• Соответствующие кавычки и скобки\n• Допустимый синтаксис YAML",
|
||||
"schemaValidationError": "Недопустимый формат пользовательских режимов в .roomodes:\n{{issues}}",
|
||||
"invalidFormat": "Недопустимый формат пользовательских режимов. Убедись, что твои настройки соответствуют правильному формату YAML.",
|
||||
"updateFailed": "Не удалось обновить пользовательский режим: {{error}}",
|
||||
"deleteFailed": "Не удалось удалить пользовательский режим: {{error}}",
|
||||
"resetFailed": "Не удалось сбросить пользовательские режимы: {{error}}",
|
||||
"modeNotFound": "Ошибка записи: Режим не найден",
|
||||
"noWorkspaceForProject": "Не найдена папка рабочего пространства для режима, специфичного для проекта"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Ваша организация требует аутентификации Roo Code Cloud. Войдите в систему, чтобы продолжить.",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": ".roomodes dosyasının {{line}}. satırında geçersiz YAML. Kontrol et:\n• Doğru girinti (tab değil boşluk kullan)\n• Eşleşen tırnak işaretleri ve parantezler\n• Geçerli YAML sözdizimi",
|
||||
"schemaValidationError": ".roomodes'ta geçersiz özel mod formatı:\n{{issues}}",
|
||||
"invalidFormat": "Geçersiz özel mod formatı. Ayarlarının doğru YAML formatını takip ettiğinden emin ol.",
|
||||
"updateFailed": "Özel mod güncellemesi başarısız: {{error}}",
|
||||
"deleteFailed": "Özel mod silme başarısız: {{error}}",
|
||||
"resetFailed": "Özel modları sıfırlama başarısız: {{error}}",
|
||||
"modeNotFound": "Yazma hatası: Mod bulunamadı",
|
||||
"noWorkspaceForProject": "Proje özel modu için çalışma alanı klasörü bulunamadı"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Kuruluşunuz Roo Code Cloud kimlik doğrulaması gerektiriyor. Devam etmek için giriş yapın.",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": "YAML không hợp lệ trong tệp .roomodes tại dòng {{line}}. Vui lòng kiểm tra:\n• Thụt lề đúng (dùng dấu cách, không dùng tab)\n• Dấu ngoặc kép và ngoặc đơn khớp nhau\n• Cú pháp YAML hợp lệ",
|
||||
"schemaValidationError": "Định dạng chế độ tùy chỉnh không hợp lệ trong .roomodes:\n{{issues}}",
|
||||
"invalidFormat": "Định dạng chế độ tùy chỉnh không hợp lệ. Vui lòng đảm bảo cài đặt của bạn tuân theo định dạng YAML đúng.",
|
||||
"updateFailed": "Cập nhật chế độ tùy chỉnh thất bại: {{error}}",
|
||||
"deleteFailed": "Xóa chế độ tùy chỉnh thất bại: {{error}}",
|
||||
"resetFailed": "Đặt lại chế độ tùy chỉnh thất bại: {{error}}",
|
||||
"modeNotFound": "Lỗi ghi: Không tìm thấy chế độ",
|
||||
"noWorkspaceForProject": "Không tìm thấy thư mục workspace cho chế độ dành riêng cho dự án"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Tổ chức của bạn yêu cầu xác thực Roo Code Cloud. Vui lòng đăng nhập để tiếp tục.",
|
||||
|
|
|
|||
|
|
@ -127,6 +127,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": ".roomodes 文件第 {{line}} 行 YAML 格式无效。请检查:\n• 正确的缩进(使用空格,不要使用制表符)\n• 匹配的引号和括号\n• 有效的 YAML 语法",
|
||||
"schemaValidationError": ".roomodes 中自定义模式格式无效:\n{{issues}}",
|
||||
"invalidFormat": "自定义模式格式无效。请确保你的设置遵循正确的 YAML 格式。",
|
||||
"updateFailed": "更新自定义模式失败:{{error}}",
|
||||
"deleteFailed": "删除自定义模式失败:{{error}}",
|
||||
"resetFailed": "重置自定义模式失败:{{error}}",
|
||||
"modeNotFound": "写入错误:未找到模式",
|
||||
"noWorkspaceForProject": "未找到项目特定模式的工作区文件夹"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "您的组织需要 Roo Code Cloud 身份验证。请登录以继续。",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"customModes": {
|
||||
"errors": {
|
||||
"yamlParseError": ".roomodes 檔案第 {{line}} 行 YAML 格式無效。請檢查:\n• 正確的縮排(使用空格,不要使用定位字元)\n• 匹配的引號和括號\n• 有效的 YAML 語法",
|
||||
"schemaValidationError": ".roomodes 中自訂模式格式無效:\n{{issues}}",
|
||||
"invalidFormat": "自訂模式格式無效。請確保你的設定遵循正確的 YAML 格式。",
|
||||
"updateFailed": "更新自訂模式失敗:{{error}}",
|
||||
"deleteFailed": "刪除自訂模式失敗:{{error}}",
|
||||
"resetFailed": "重設自訂模式失敗:{{error}}",
|
||||
"modeNotFound": "寫入錯誤:未找到模式",
|
||||
"noWorkspaceForProject": "未找到專案特定模式的工作區資料夾"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "您的組織需要 Roo Code Cloud 身份驗證。請登入以繼續。",
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export class SimpleInstaller {
|
|||
|
||||
// Write back to file
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
||||
const yamlContent = yaml.stringify(existingData)
|
||||
const yamlContent = yaml.stringify(existingData, { lineWidth: 0, defaultStringType: "PLAIN" })
|
||||
await fs.writeFile(filePath, yamlContent, "utf-8")
|
||||
|
||||
// Calculate approximate line number where the new mode was added
|
||||
|
|
@ -282,7 +282,11 @@ export class SimpleInstaller {
|
|||
existingData.customModes = existingData.customModes.filter((mode: any) => mode.slug !== modeData.slug)
|
||||
|
||||
// Always write back the file, even if empty
|
||||
await fs.writeFile(filePath, yaml.stringify(existingData), "utf-8")
|
||||
await fs.writeFile(
|
||||
filePath,
|
||||
yaml.stringify(existingData, { lineWidth: 0, defaultStringType: "PLAIN" }),
|
||||
"utf-8",
|
||||
)
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.code === "ENOENT") {
|
||||
|
|
|
|||
|
|
@ -92,8 +92,8 @@ async function migrateCustomModesToYaml(settingsDir: string, outputChannel: vsco
|
|||
// Parse JSON to object (using the yaml library just to be safe/consistent)
|
||||
const customModesData = yaml.parse(jsonContent)
|
||||
|
||||
// Convert to YAML
|
||||
const yamlContent = yaml.stringify(customModesData)
|
||||
// Convert to YAML with no line width limit to prevent line breaks
|
||||
const yamlContent = yaml.stringify(customModesData, { lineWidth: 0, defaultStringType: "PLAIN" })
|
||||
|
||||
// Write YAML file
|
||||
await fs.writeFile(newYamlPath, yamlContent, "utf-8")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue