@vibe-dev-kit/cli
Version:
Advanced Command-line toolkit that analyzes your codebase and deploys project-aware rules, memories, commands and agents to any AI coding assistant - VDK is the world's first Vibe Development Kit
173 lines (149 loc) • 4.99 kB
JavaScript
/**
* Schema Validator Utility
* -----------------------
* Centralized validation for VDK schemas including commands and blueprints
*/
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SCHEMAS_DIR = path.join(__dirname, '../schemas');
// Cache for loaded schemas
const schemaCache = new Map();
/**
* Load schema from file with caching
*/
async function loadSchema(schemaName) {
if (schemaCache.has(schemaName)) {
return schemaCache.get(schemaName);
}
try {
const schemaPath = path.join(SCHEMAS_DIR, `${schemaName}.json`);
const schemaContent = await fs.readFile(schemaPath, 'utf8');
const schema = JSON.parse(schemaContent);
schemaCache.set(schemaName, schema);
return schema;
} catch (error) {
throw new Error(`Failed to load schema '${schemaName}': ${error.message}`);
}
}
/**
* Validate data against a schema
*/
export async function validateSchema(data, schemaName) {
const schema = await loadSchema(schemaName);
const errors = [];
// Validate required fields
if (schema.required) {
for (const field of schema.required) {
if (!(field in data)) {
errors.push(`Missing required field: ${field}`);
}
}
}
// Validate properties
if (schema.properties) {
for (const [field, fieldSchema] of Object.entries(schema.properties)) {
const value = data[field];
if (value === undefined || value === null) {
continue; // Skip validation for missing optional fields
}
// Type validation
const expectedType = fieldSchema.type;
const actualType = Array.isArray(value) ? 'array' : typeof value;
if (expectedType && actualType !== expectedType) {
errors.push(`Field '${field}' should be of type ${expectedType}, got ${actualType}`);
continue;
}
// String validations
if (expectedType === 'string' && typeof value === 'string') {
// Pattern validation
if (fieldSchema.pattern) {
const pattern = new RegExp(fieldSchema.pattern);
if (!pattern.test(value)) {
errors.push(`Field '${field}' does not match required pattern`);
}
}
// Length validation
if (fieldSchema.minLength && value.length < fieldSchema.minLength) {
errors.push(`Field '${field}' must be at least ${fieldSchema.minLength} characters`);
}
if (fieldSchema.maxLength && value.length > fieldSchema.maxLength) {
errors.push(`Field '${field}' must not exceed ${fieldSchema.maxLength} characters`);
}
// Enum validation
if (fieldSchema.enum && !fieldSchema.enum.includes(value)) {
errors.push(`Field '${field}' must be one of: ${fieldSchema.enum.join(', ')}`);
}
}
// Array validation
if (expectedType === 'array' && Array.isArray(value)) {
if (fieldSchema.items && fieldSchema.items.type) {
for (const [index, item] of value.entries()) {
const itemType = typeof item;
if (itemType !== fieldSchema.items.type) {
errors.push(
`Array '${field}' item at index ${index} should be ${fieldSchema.items.type}, got ${itemType}`
);
}
}
}
}
// Object validation (simplified)
if (expectedType === 'object' && typeof value === 'object' && fieldSchema.properties) {
for (const [subField, subSchema] of Object.entries(fieldSchema.properties)) {
const subValue = value[subField];
if (subValue !== undefined && subSchema.type) {
const subType = Array.isArray(subValue) ? 'array' : typeof subValue;
if (subType !== subSchema.type) {
errors.push(
`Object '${field}.${subField}' should be ${subSchema.type}, got ${subType}`
);
}
}
}
}
}
}
return {
valid: errors.length === 0,
errors,
};
}
/**
* Validate Claude Code command
*/
export async function validateCommand(commandData) {
return await validateSchema(commandData, 'command-schema');
}
/**
* Validate VDK Blueprint
*/
export async function validateBlueprint(blueprintData) {
return await validateSchema(blueprintData, 'blueprint-schema');
}
/**
* Get all available schemas
*/
export async function getAvailableSchemas() {
try {
const files = await fs.readdir(SCHEMAS_DIR);
return files.filter((file) => file.endsWith('.json')).map((file) => file.replace('.json', ''));
} catch (error) {
console.warn(`Could not read schemas directory: ${error.message}`);
return [];
}
}
/**
* Clear schema cache (useful for testing)
*/
export function clearSchemaCache() {
schemaCache.clear();
}
export default {
validateSchema,
validateCommand,
validateBlueprint,
getAvailableSchemas,
clearSchemaCache,
};