From 8725668865f5a1c467eabb83d7a29d7ba47c639d Mon Sep 17 00:00:00 2001 From: Roo Code Date: Wed, 8 Apr 2026 22:03:34 +0000 Subject: [PATCH] refactor: move schema tests and ajv dep to packages/types, extract shared schema builder - Remove ajv from src/package.json (extension workspace) since it was only used for schema validation tests unrelated to extension runtime code - Add ajv as devDependency to packages/types/package.json where the schema generation already lives - Move AJV validation tests from src/utils/__tests__/ to packages/types/src/__tests__/ alongside the sync test - Extract shared schema-building logic into packages/types/src/roomodes-schema.ts to eliminate duplication between the generator script and the sync test - Regenerate schemas/roomodes.json via the updated generator --- packages/types/package.json | 1 + .../types/scripts/generate-roomodes-schema.ts | 62 +------------------ .../__tests__/roomodes-schema-sync.spec.ts | 32 +--------- .../src}/__tests__/roomodes-schema.spec.ts | 15 ++--- packages/types/src/roomodes-schema.ts | 58 +++++++++++++++++ pnpm-lock.yaml | 6 +- src/package.json | 1 - 7 files changed, 74 insertions(+), 101 deletions(-) rename {src/utils => packages/types/src}/__tests__/roomodes-schema.spec.ts (95%) create mode 100644 packages/types/src/roomodes-schema.ts diff --git a/packages/types/package.json b/packages/types/package.json index 538268df47..b9af606172 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -32,6 +32,7 @@ "@types/node": "^24.1.0", "globals": "^16.3.0", "tsup": "^8.4.0", + "ajv": "^8.18.0", "vitest": "^3.2.3", "zod-to-json-schema": "^3.25.1" } diff --git a/packages/types/scripts/generate-roomodes-schema.ts b/packages/types/scripts/generate-roomodes-schema.ts index ce22957504..a7d6c0b978 100644 --- a/packages/types/scripts/generate-roomodes-schema.ts +++ b/packages/types/scripts/generate-roomodes-schema.ts @@ -11,69 +11,11 @@ import * as fs from "fs" import * as path from "path" import { fileURLToPath } from "url" -import { zodToJsonSchema } from "zod-to-json-schema" -import { z } from "zod" -import { toolGroups, deprecatedToolGroups } from "../src/tool.js" -import { groupOptionsSchema, modeConfigSchema } from "../src/mode.js" +import { generateRoomodesJsonSchema } from "../src/roomodes-schema.js" -// --------------------------------------------------------------------------- -// 1. Build a ToolGroup enum that includes deprecated groups so existing -// configs still validate. -// --------------------------------------------------------------------------- -const allToolGroups = [...toolGroups, ...deprecatedToolGroups] as [string, ...string[]] -const allToolGroupsSchema = z.enum(allToolGroups) +const jsonSchema = generateRoomodesJsonSchema() -// --------------------------------------------------------------------------- -// 2. Build a GroupEntry schema that uses the extended tool group list. -// --------------------------------------------------------------------------- -const groupEntrySchema = z.union([allToolGroupsSchema, z.tuple([allToolGroupsSchema, groupOptionsSchema])]) - -// --------------------------------------------------------------------------- -// 3. Build the RuleFile schema (used during import/export but not part of -// the core Zod types). -// --------------------------------------------------------------------------- -const ruleFileSchema = z.object({ - relativePath: z.string(), - content: z.string().optional(), -}) - -// --------------------------------------------------------------------------- -// 4. Build an extended ModeConfig schema that includes rulesFiles and uses -// the extended groups (with deprecated entries). -// --------------------------------------------------------------------------- -const exportedModeConfigSchema = modeConfigSchema.omit({ groups: true }).extend({ - groups: z.array(groupEntrySchema), - rulesFiles: z.array(ruleFileSchema).optional(), -}) - -// --------------------------------------------------------------------------- -// 5. Build the top-level .roomodes schema. -// --------------------------------------------------------------------------- -const roomodesSchema = z - .object({ - customModes: z.array(exportedModeConfigSchema), - }) - .strict() - -// --------------------------------------------------------------------------- -// 6. Convert to JSON Schema (draft-07). -// --------------------------------------------------------------------------- -const jsonSchema = zodToJsonSchema(roomodesSchema, { - $refStrategy: "none", - target: "jsonSchema7", -}) as Record - -// --------------------------------------------------------------------------- -// 7. Add metadata. -// --------------------------------------------------------------------------- -jsonSchema["$id"] = "https://github.com/RooCodeInc/Roo-Code/blob/main/schemas/roomodes.json" -jsonSchema["title"] = "Roo Code Custom Modes" -jsonSchema["description"] = "Schema for .roomodes configuration files used by Roo Code to define custom modes." - -// --------------------------------------------------------------------------- -// 8. Write to disk. -// --------------------------------------------------------------------------- const __dirname = path.dirname(fileURLToPath(import.meta.url)) const repoRoot = path.resolve(__dirname, "../../..") const outPath = path.join(repoRoot, "schemas", "roomodes.json") diff --git a/packages/types/src/__tests__/roomodes-schema-sync.spec.ts b/packages/types/src/__tests__/roomodes-schema-sync.spec.ts index 1347bb49b5..fb053c2fc2 100644 --- a/packages/types/src/__tests__/roomodes-schema-sync.spec.ts +++ b/packages/types/src/__tests__/roomodes-schema-sync.spec.ts @@ -2,11 +2,8 @@ import { describe, it, expect } from "vitest" import * as fs from "fs" import * as path from "path" import { fileURLToPath } from "url" -import { zodToJsonSchema } from "zod-to-json-schema" -import { z } from "zod" -import { toolGroups, deprecatedToolGroups } from "../tool.js" -import { groupOptionsSchema, modeConfigSchema } from "../mode.js" +import { generateRoomodesJsonSchema } from "../roomodes-schema.js" /** * This test verifies that the checked-in schemas/roomodes.json matches what @@ -22,32 +19,7 @@ describe("roomodes schema sync", () => { const schemaPath = path.resolve(__dirname, "../../../../schemas/roomodes.json") const checkedIn = JSON.parse(fs.readFileSync(schemaPath, "utf-8")) - // Reproduce the same generation logic as scripts/generate-roomodes-schema.ts - const allToolGroups = [...toolGroups, ...deprecatedToolGroups] as [string, ...string[]] - const allToolGroupsSchema = z.enum(allToolGroups) - const groupEntrySchema = z.union([allToolGroupsSchema, z.tuple([allToolGroupsSchema, groupOptionsSchema])]) - const ruleFileSchema = z.object({ - relativePath: z.string(), - content: z.string().optional(), - }) - const exportedModeConfigSchema = modeConfigSchema.omit({ groups: true }).extend({ - groups: z.array(groupEntrySchema), - rulesFiles: z.array(ruleFileSchema).optional(), - }) - const roomodesSchema = z - .object({ - customModes: z.array(exportedModeConfigSchema), - }) - .strict() - - const generated = zodToJsonSchema(roomodesSchema, { - $refStrategy: "none", - target: "jsonSchema7", - }) as Record - - generated["$id"] = "https://github.com/RooCodeInc/Roo-Code/blob/main/schemas/roomodes.json" - generated["title"] = "Roo Code Custom Modes" - generated["description"] = "Schema for .roomodes configuration files used by Roo Code to define custom modes." + const generated = generateRoomodesJsonSchema() expect(checkedIn).toEqual(generated) }) diff --git a/src/utils/__tests__/roomodes-schema.spec.ts b/packages/types/src/__tests__/roomodes-schema.spec.ts similarity index 95% rename from src/utils/__tests__/roomodes-schema.spec.ts rename to packages/types/src/__tests__/roomodes-schema.spec.ts index 2b20dcb41d..f3530a225d 100644 --- a/src/utils/__tests__/roomodes-schema.spec.ts +++ b/packages/types/src/__tests__/roomodes-schema.spec.ts @@ -3,23 +3,24 @@ * using AJV. The schema itself is dynamically generated from the Zod types in * packages/types/src/mode.ts -- see packages/types/scripts/generate-roomodes-schema.ts. * - * A separate drift-detection test in packages/types ensures the checked-in - * schema stays in sync with the Zod source of truth. + * A separate drift-detection test (roomodes-schema-sync.spec.ts) ensures the + * checked-in schema stays in sync with the Zod source of truth. */ import { describe, it, expect, beforeAll } from "vitest" -import Ajv from "ajv" +import Ajv, { type ValidateFunction } from "ajv" import * as fs from "fs" import * as path from "path" +import { fileURLToPath } from "url" describe("roomodes JSON schema", () => { - let ajv: Ajv let schema: Record - let validate: ReturnType + let validate: ValidateFunction beforeAll(() => { - const schemaPath = path.resolve(__dirname, "../../../schemas/roomodes.json") + const __dirname = path.dirname(fileURLToPath(import.meta.url)) + const schemaPath = path.resolve(__dirname, "../../../../schemas/roomodes.json") schema = JSON.parse(fs.readFileSync(schemaPath, "utf-8")) - ajv = new Ajv({ strict: false }) + const ajv = new Ajv.default({ strict: false }) validate = ajv.compile(schema) }) diff --git a/packages/types/src/roomodes-schema.ts b/packages/types/src/roomodes-schema.ts new file mode 100644 index 0000000000..6283395686 --- /dev/null +++ b/packages/types/src/roomodes-schema.ts @@ -0,0 +1,58 @@ +/** + * Builds the Zod schema for .roomodes configuration files and converts it + * to JSON Schema (draft-07). This module is the single source of truth for + * both the generator script (scripts/generate-roomodes-schema.ts) and the + * drift-detection test. + */ + +import { z } from "zod" +import { zodToJsonSchema } from "zod-to-json-schema" + +import { toolGroups, deprecatedToolGroups } from "./tool.js" +import { groupOptionsSchema, modeConfigSchema } from "./mode.js" + +// Build a ToolGroup enum that includes deprecated groups so existing configs +// still validate. +const allToolGroups = [...toolGroups, ...deprecatedToolGroups] as [string, ...string[]] +const allToolGroupsSchema = z.enum(allToolGroups) + +// Build a GroupEntry schema that uses the extended tool group list. +const groupEntrySchema = z.union([allToolGroupsSchema, z.tuple([allToolGroupsSchema, groupOptionsSchema])]) + +// Build the RuleFile schema (used during import/export but not part of the +// core Zod types). +const ruleFileSchema = z.object({ + relativePath: z.string(), + content: z.string().optional(), +}) + +// Build an extended ModeConfig schema that includes rulesFiles and uses the +// extended groups (with deprecated entries). +const exportedModeConfigSchema = modeConfigSchema.omit({ groups: true }).extend({ + groups: z.array(groupEntrySchema), + rulesFiles: z.array(ruleFileSchema).optional(), +}) + +// Build the top-level .roomodes schema. +const roomodesZodSchema = z + .object({ + customModes: z.array(exportedModeConfigSchema), + }) + .strict() + +/** + * Generates the JSON Schema object for .roomodes configuration files. + * Includes metadata fields ($id, title, description). + */ +export function generateRoomodesJsonSchema(): Record { + const jsonSchema = zodToJsonSchema(roomodesZodSchema, { + $refStrategy: "none", + target: "jsonSchema7", + }) as Record + + jsonSchema["$id"] = "https://github.com/RooCodeInc/Roo-Code/blob/main/schemas/roomodes.json" + jsonSchema["title"] = "Roo Code Custom Modes" + jsonSchema["description"] = "Schema for .roomodes configuration files used by Roo Code to define custom modes." + + return jsonSchema +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 17c9cc12fd..d48ccd3376 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -722,6 +722,9 @@ importers: '@types/node': specifier: ^24.1.0 version: 24.2.1 + ajv: + specifier: ^8.18.0 + version: 8.18.0 globals: specifier: ^16.3.0 version: 16.3.0 @@ -1104,9 +1107,6 @@ importers: ai: specifier: ^6.0.75 version: 6.0.77(zod@3.25.76) - ajv: - specifier: ^8.18.0 - version: 8.18.0 esbuild-wasm: specifier: ^0.25.0 version: 0.25.12 diff --git a/src/package.json b/src/package.json index 46b554d5b0..241312ac8c 100644 --- a/src/package.json +++ b/src/package.json @@ -567,7 +567,6 @@ "@vscode/test-electron": "^2.5.2", "@vscode/vsce": "3.3.2", "ai": "^6.0.75", - "ajv": "^8.18.0", "esbuild-wasm": "^0.25.0", "execa": "^9.5.2", "glob": "^11.1.0",