@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
139 lines • 5.48 kB
JavaScript
/**
* Schema Parser
* @description Parses TypeScript schema files to extract JSON schemas
*
* Purpose: Safely parse fields.generated.ts without using eval()
* This parser extracts schema definitions from TypeScript code.
*
* @author Optimizely MCP Server
* @version 1.0.0
*/
import { readFileSync } from 'fs';
import { getLogger } from '../logging/Logger.js';
export class SchemaParser {
logger = getLogger();
/**
* Parse schemas from TypeScript file content
*/
parseSchemas(content) {
this.logger.debug('Parsing schemas from TypeScript content');
try {
// Find the schemas export
const schemasMatch = content.match(/export\s+const\s+schemas\s*=\s*({[\s\S]*?})\s*(?:as\s+const\s*)?;/);
if (!schemasMatch) {
throw new Error('Could not find schemas export');
}
let schemasText = schemasMatch[1];
// Remove TypeScript type annotations
schemasText = this.removeTypeAnnotations(schemasText);
// Convert to valid JSON
schemasText = this.convertToJson(schemasText);
// Parse the JSON
const schemas = JSON.parse(schemasText);
this.logger.info(`Successfully parsed ${Object.keys(schemas).length} schemas`);
return schemas;
}
catch (error) {
this.logger.error({ error }, 'Failed to parse schemas');
// Fallback: Try to extract individual schema objects
return this.extractIndividualSchemas(content);
}
}
/**
* Remove TypeScript type annotations
*/
removeTypeAnnotations(text) {
// Remove type annotations like ": OpenAPIV3.SchemaObject"
text = text.replace(/:\s*OpenAPIV3\.\w+/g, '');
text = text.replace(/:\s*\w+\[\]/g, ''); // Remove array type annotations
text = text.replace(/:\s*\w+/g, ''); // Remove simple type annotations
text = text.replace(/as\s+const/g, ''); // Remove "as const"
text = text.replace(/as\s+\w+/g, ''); // Remove other "as" type assertions
return text;
}
/**
* Convert TypeScript object to JSON
*/
convertToJson(text) {
// Replace single quotes with double quotes
text = text.replace(/'/g, '"');
// Handle unquoted property names
text = text.replace(/(\w+):/g, '"$1":');
// Fix any double-quoted quotes
text = text.replace(/""/g, '"');
// Handle trailing commas
text = text.replace(/,(\s*[}\]])/g, '$1');
// Handle undefined values
text = text.replace(/:\s*undefined/g, ': null');
// Handle template literals (convert to regular strings)
text = text.replace(/`([^`]*)`/g, '"$1"');
// Handle multi-line strings
text = text.replace(/\n/g, '\\n');
return text;
}
/**
* Extract individual schemas as fallback
*/
extractIndividualSchemas(content) {
const schemas = {};
// Pattern to match individual schema definitions
const schemaPattern = /(\w+):\s*{[^}]*type:\s*["'](\w+)["'][^}]*}/g;
let match;
while ((match = schemaPattern.exec(content)) !== null) {
const schemaName = match[1];
const schemaType = match[2];
// Create a basic schema object
schemas[schemaName] = {
type: schemaType,
properties: {}
};
}
// Try to extract more detailed schemas
const detailedPattern = /(\w+):\s*({[\s\S]*?^\s*})/gm;
content.split('\n').forEach((line, index, lines) => {
if (line.includes(': {') && line.trim().match(/^\w+:/)) {
const schemaName = line.trim().split(':')[0].trim();
let braceCount = 1;
let schemaText = '{';
for (let i = index + 1; i < lines.length && braceCount > 0; i++) {
const currentLine = lines[i];
schemaText += '\n' + currentLine;
// Count braces
for (const char of currentLine) {
if (char === '{')
braceCount++;
if (char === '}')
braceCount--;
}
}
if (braceCount === 0) {
try {
const cleanedSchema = this.removeTypeAnnotations(schemaText);
const jsonSchema = this.convertToJson(cleanedSchema);
schemas[schemaName] = JSON.parse(jsonSchema);
}
catch (e) {
this.logger.warn({ error: e, schemaName }, `Failed to parse schema ${schemaName}`);
}
}
}
});
this.logger.info(`Extracted ${Object.keys(schemas).length} schemas using fallback method`);
return schemas;
}
/**
* Load and parse schemas from file
*/
loadSchemasFromFile(filePath) {
try {
const content = readFileSync(filePath, 'utf-8');
return this.parseSchemas(content);
}
catch (error) {
this.logger.error({ error, filePath }, `Failed to load schemas from ${filePath}`);
throw error;
}
}
}
export const schemaParser = new SchemaParser();
//# sourceMappingURL=SchemaParser.js.map