Fixing eslint issues
Some checks failed
Main Workflow / Test and Build (20.x) (push) Successful in 3m58s
Main Workflow / Security Audit (push) Successful in 4m10s
Main Workflow / Test and Build (18.x) (push) Failing after 2m46s
Main Workflow / Build Release Artifacts (push) Has been skipped
Main Workflow / Code Quality Check (push) Failing after 1m51s
Main Workflow / Notification (push) Failing after 21s

This commit is contained in:
2025-07-23 05:20:31 +00:00
parent 6aa7e91874
commit 9d5dc9ae7d
12 changed files with 432 additions and 570 deletions

126
eslint.config.js Normal file
View File

@ -0,0 +1,126 @@
import js from '@eslint/js';
import tseslint from '@typescript-eslint/eslint-plugin';
import tsparser from '@typescript-eslint/parser';
export default [
// Base JavaScript configuration
js.configs.recommended,
// Configuration for TypeScript files
{
files: ['**/*.ts', '**/*.tsx'],
ignores: ['**/*.test.ts', '**/*.spec.ts', 'src/tests/**/*.ts'],
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
project: './tsconfig.json',
},
globals: {
console: 'readonly',
process: 'readonly',
Buffer: 'readonly',
global: 'readonly',
},
},
plugins: {
'@typescript-eslint': tseslint,
},
rules: {
// TypeScript recommended rules
...tseslint.configs.recommended.rules,
// Custom rules for the project
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-var-requires': 'error',
// General JavaScript/TypeScript rules
'no-console': 'warn',
'no-debugger': 'error',
'no-duplicate-imports': 'error',
'no-unused-expressions': 'error',
'prefer-const': 'error',
'no-var': 'error',
// Style rules
'indent': ['error', 2],
'quotes': ['error', 'single'],
'semi': ['error', 'always'],
'comma-dangle': ['error', 'always-multiline'],
'object-curly-spacing': ['error', 'always'],
'array-bracket-spacing': ['error', 'never'],
},
},
// Specific configuration for test files
{
files: ['**/*.test.ts', '**/*.spec.ts', 'src/tests/**/*.ts'],
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
globals: {
console: 'readonly',
process: 'readonly',
Buffer: 'readonly',
global: 'writable',
describe: 'readonly',
it: 'readonly',
test: 'readonly',
expect: 'readonly',
beforeEach: 'readonly',
afterEach: 'readonly',
beforeAll: 'readonly',
afterAll: 'readonly',
jest: 'readonly',
require: 'readonly',
},
},
plugins: {
'@typescript-eslint': tseslint,
},
rules: {
// Allow console.log in tests
'no-console': 'off',
// Allow any in tests for mocking
'@typescript-eslint/no-explicit-any': 'off',
// Allow unused vars in tests (for setup)
'@typescript-eslint/no-unused-vars': 'off',
},
},
// Configuration for JavaScript files (if any)
{
files: ['**/*.js', '**/*.mjs'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
rules: {
'no-console': 'warn',
'no-debugger': 'error',
'prefer-const': 'error',
'no-var': 'error',
},
},
// Files and directories to ignore
{
ignores: [
'node_modules/**',
'build/**',
'dist/**',
'*.config.js',
'coverage/**',
'.git/**',
'.vscode/**',
'*.min.js',
],
},
];

View File

@ -1,5 +1,5 @@
// index.d.ts
declare module "*.gql" {
declare module '*.gql' {
const content: any;
export default content;
}

View File

@ -6,98 +6,98 @@ const DIAGRAM_KEYWORDS: Record<DiagramType, string[]> = {
'proceso', 'flujo', 'workflow', 'aprobación', 'tarea', 'decisión', 'gateway', 'evento',
'actividad', 'secuencia', 'paralelo', 'exclusivo', 'inicio', 'fin', 'subprocess',
'process', 'flow', 'approval', 'task', 'decision', 'activity', 'sequence', 'parallel',
'exclusive', 'start', 'end', 'business process'
'exclusive', 'start', 'end', 'business process',
],
[DiagramType.BPMN_COLLABORATION]: [
'colaboración', 'participante', 'pool', 'lane', 'mensaje', 'collaboration',
'participant', 'message', 'proceso', 'flujo', 'workflow'
'participant', 'message', 'proceso', 'flujo', 'workflow',
],
[DiagramType.BPMN_CHOREOGRAPHY]: [
'coreografía', 'intercambio', 'mensaje', 'choreography', 'exchange', 'message',
'comunicación', 'communication', 'protocolo', 'protocol'
'comunicación', 'communication', 'protocolo', 'protocol',
],
[DiagramType.ER_DIAGRAM]: [
'base de datos', 'tabla', 'entidad', 'relación', 'campo', 'clave', 'foreign key',
'primary key', 'atributo', 'cardinalidad', 'normalización', 'esquema',
'database', 'table', 'entity', 'relationship', 'field', 'key', 'attribute',
'cardinality', 'normalization', 'schema', 'sql', 'modelo de datos'
'cardinality', 'normalization', 'schema', 'sql', 'modelo de datos',
],
[DiagramType.DATABASE_SCHEMA]: [
'esquema', 'schema', 'base de datos', 'database', 'tabla', 'table', 'sql'
'esquema', 'schema', 'base de datos', 'database', 'tabla', 'table', 'sql',
],
[DiagramType.CONCEPTUAL_MODEL]: [
'modelo conceptual', 'conceptual model', 'concepto', 'concept', 'dominio', 'domain'
'modelo conceptual', 'conceptual model', 'concepto', 'concept', 'dominio', 'domain',
],
[DiagramType.SYSTEM_ARCHITECTURE]: [
'arquitectura', 'sistema', 'componente', 'servicio', 'api', 'microservicio', 'capa',
'módulo', 'interfaz', 'dependencia', 'infraestructura', 'servidor',
'architecture', 'system', 'component', 'service', 'microservice', 'layer',
'module', 'interface', 'dependency', 'infrastructure', 'server', 'backend', 'frontend'
'module', 'interface', 'dependency', 'infrastructure', 'server', 'backend', 'frontend',
],
[DiagramType.MICROSERVICES]: [
'microservicio', 'microservice', 'servicio', 'service', 'api', 'contenedor', 'container'
'microservicio', 'microservice', 'servicio', 'service', 'api', 'contenedor', 'container',
],
[DiagramType.LAYERED_ARCHITECTURE]: [
'capas', 'layers', 'arquitectura por capas', 'layered architecture', 'nivel', 'tier'
'capas', 'layers', 'arquitectura por capas', 'layered architecture', 'nivel', 'tier',
],
[DiagramType.C4_CONTEXT]: [
'c4', 'contexto', 'context', 'sistema', 'system', 'usuario', 'user', 'actor'
'c4', 'contexto', 'context', 'sistema', 'system', 'usuario', 'user', 'actor',
],
[DiagramType.C4_CONTAINER]: [
'c4', 'contenedor', 'container', 'aplicación', 'application', 'servicio', 'service'
'c4', 'contenedor', 'container', 'aplicación', 'application', 'servicio', 'service',
],
[DiagramType.C4_COMPONENT]: [
'c4', 'componente', 'component', 'módulo', 'module', 'clase', 'class'
'c4', 'componente', 'component', 'módulo', 'module', 'clase', 'class',
],
[DiagramType.UML_CLASS]: [
'clase', 'objeto', 'herencia', 'polimorfismo', 'encapsulación', 'método', 'atributo',
'class', 'object', 'inheritance', 'polymorphism', 'encapsulation', 'method',
'uml', 'orientado a objetos', 'oop'
'uml', 'orientado a objetos', 'oop',
],
[DiagramType.UML_SEQUENCE]: [
'secuencia', 'sequence', 'mensaje', 'message', 'tiempo', 'time', 'interacción', 'interaction'
'secuencia', 'sequence', 'mensaje', 'message', 'tiempo', 'time', 'interacción', 'interaction',
],
[DiagramType.UML_USE_CASE]: [
'caso de uso', 'use case', 'actor', 'funcionalidad', 'functionality', 'requisito', 'requirement'
'caso de uso', 'use case', 'actor', 'funcionalidad', 'functionality', 'requisito', 'requirement',
],
[DiagramType.UML_ACTIVITY]: [
'actividad', 'activity', 'flujo', 'flow', 'acción', 'action', 'proceso', 'process'
'actividad', 'activity', 'flujo', 'flow', 'acción', 'action', 'proceso', 'process',
],
[DiagramType.UML_STATE]: [
'estado', 'state', 'transición', 'transition', 'máquina de estados', 'state machine'
'estado', 'state', 'transición', 'transition', 'máquina de estados', 'state machine',
],
[DiagramType.UML_COMPONENT]: [
'componente', 'component', 'interfaz', 'interface', 'dependencia', 'dependency'
'componente', 'component', 'interfaz', 'interface', 'dependencia', 'dependency',
],
[DiagramType.UML_DEPLOYMENT]: [
'despliegue', 'deployment', 'nodo', 'node', 'artefacto', 'artifact', 'hardware'
'despliegue', 'deployment', 'nodo', 'node', 'artefacto', 'artifact', 'hardware',
],
[DiagramType.NETWORK_TOPOLOGY]: [
'red', 'router', 'switch', 'firewall', 'vlan', 'subred', 'ip', 'tcp', 'protocolo',
'network', 'topology', 'subnet', 'protocol', 'ethernet', 'wifi', 'lan', 'wan'
'network', 'topology', 'subnet', 'protocol', 'ethernet', 'wifi', 'lan', 'wan',
],
[DiagramType.INFRASTRUCTURE]: [
'infraestructura', 'infrastructure', 'servidor', 'server', 'hardware', 'datacenter'
'infraestructura', 'infrastructure', 'servidor', 'server', 'hardware', 'datacenter',
],
[DiagramType.CLOUD_ARCHITECTURE]: [
'nube', 'cloud', 'aws', 'azure', 'gcp', 'kubernetes', 'docker', 'contenedor'
'nube', 'cloud', 'aws', 'azure', 'gcp', 'kubernetes', 'docker', 'contenedor',
],
[DiagramType.FLOWCHART]: [
'diagrama de flujo', 'flowchart', 'algoritmo', 'lógica', 'condición', 'bucle',
'algorithm', 'logic', 'condition', 'loop', 'if', 'while', 'for'
'algorithm', 'logic', 'condition', 'loop', 'if', 'while', 'for',
],
[DiagramType.ORGCHART]: [
'organigrama', 'orgchart', 'jerarquía', 'hierarchy', 'organización', 'organization'
'organigrama', 'orgchart', 'jerarquía', 'hierarchy', 'organización', 'organization',
],
[DiagramType.MINDMAP]: [
'mapa mental', 'mindmap', 'idea', 'concepto', 'brainstorming', 'lluvia de ideas'
'mapa mental', 'mindmap', 'idea', 'concepto', 'brainstorming', 'lluvia de ideas',
],
[DiagramType.WIREFRAME]: [
'wireframe', 'mockup', 'prototipo', 'prototype', 'interfaz', 'interface', 'ui', 'ux'
'wireframe', 'mockup', 'prototipo', 'prototype', 'interfaz', 'interface', 'ui', 'ux',
],
[DiagramType.GANTT]: [
'gantt', 'cronograma', 'timeline', 'proyecto', 'project', 'tarea', 'task', 'tiempo'
]
'gantt', 'cronograma', 'timeline', 'proyecto', 'project', 'tarea', 'task', 'tiempo',
],
};
// Entity extraction patterns for different diagram types
@ -105,18 +105,18 @@ const ENTITY_PATTERNS: Partial<Record<DiagramType, Record<string, RegExp>>> = {
[DiagramType.BPMN_PROCESS]: {
tasks: /(?:tarea|task|actividad|activity|paso|step)\s*:?\s*([^,.\n]+)/gi,
events: /(?:evento|event|inicio|start|fin|end)\s*:?\s*([^,.\n]+)/gi,
decisions: /(?:decisión|decision|gateway|condición|condition)\s*:?\s*([^,.\n]+)/gi
decisions: /(?:decisión|decision|gateway|condición|condition)\s*:?\s*([^,.\n]+)/gi,
},
[DiagramType.ER_DIAGRAM]: {
entities: /(?:tabla|table|entidad|entity)\s*:?\s*([^,.\n]+)/gi,
attributes: /(?:campo|field|atributo|attribute|columna|column)\s*:?\s*([^,.\n]+)/gi,
relationships: /(?:relación|relationship|conecta|connects?|relaciona)\s*([^,.\n]+)/gi
relationships: /(?:relación|relationship|conecta|connects?|relaciona)\s*([^,.\n]+)/gi,
},
[DiagramType.SYSTEM_ARCHITECTURE]: {
components: /(?:componente|component|servicio|service|módulo|module)\s*:?\s*([^,.\n]+)/gi,
layers: /(?:capa|layer|nivel|tier)\s*:?\s*([^,.\n]+)/gi,
services: /(?:api|servicio|service|microservicio|microservice)\s*:?\s*([^,.\n]+)/gi
}
services: /(?:api|servicio|service|microservicio|microservice)\s*:?\s*([^,.\n]+)/gi,
},
};
/**
@ -147,7 +147,7 @@ export const analyzeDiagramDescription = async (description: string): Promise<Di
entities,
relationships,
keywords,
reasoning: generateReasoning(bestMatch, keywords, entities)
reasoning: generateReasoning(bestMatch, keywords, entities),
};
};
@ -177,7 +177,7 @@ const getBestDiagramType = (scores: Record<DiagramType, number>): { type: Diagra
return { type: DiagramType.FLOWCHART, confidence: 0.3 };
}
const bestType = entries.find(([_, score]) => score === maxScore)?.[0] as DiagramType;
const bestType = entries.find(([_category, score]) => score === maxScore)?.[0] as DiagramType;
const totalKeywords = DIAGRAM_KEYWORDS[bestType]?.length || 1;
const confidence = Math.min(maxScore / totalKeywords, 1);
@ -197,7 +197,7 @@ const extractEntities = (description: string, diagramType: DiagramType): string[
}
// Extract entities using specific patterns
for (const [category, pattern] of Object.entries(patterns)) {
for (const pattern of Object.values(patterns)) {
const matches = [...description.matchAll(pattern)];
entities.push(...matches.map(match => match[1]?.trim()).filter(Boolean));
}
@ -221,7 +221,7 @@ const extractGenericEntities = (description: string): string[] => {
const entities = [
...capitalizedWords,
...quotedStrings.map(s => s.replace(/"/g, ''))
...quotedStrings.map(s => s.replace(/"/g, '')),
];
return [...new Set(entities)].slice(0, 10); // Limit to 10 entities
@ -233,7 +233,7 @@ const extractGenericEntities = (description: string): string[] => {
const extractRelationships = (
description: string,
entities: string[],
diagramType: DiagramType
diagramType: DiagramType,
): Array<{ from: string; to: string; type: string; label?: string }> => {
const relationships: Array<{ from: string; to: string; type: string; label?: string }> = [];
@ -241,7 +241,7 @@ const extractRelationships = (
const relationshipKeywords = [
'conecta', 'connects', 'relaciona', 'relates', 'depende', 'depends',
'hereda', 'inherits', 'implementa', 'implements', 'usa', 'uses',
'tiene', 'has', 'contiene', 'contains', 'pertenece', 'belongs'
'tiene', 'has', 'contiene', 'contains', 'pertenece', 'belongs',
];
// Simple relationship extraction based on proximity and keywords
@ -254,7 +254,7 @@ const extractRelationships = (
const pattern = new RegExp(
`${entity1}.{0,50}(?:${relationshipKeywords.join('|')}).{0,50}${entity2}|` +
`${entity2}.{0,50}(?:${relationshipKeywords.join('|')}).{0,50}${entity1}`,
'i'
'i',
);
if (pattern.test(description)) {
@ -262,7 +262,7 @@ const extractRelationships = (
from: entity1,
to: entity2,
type: getRelationshipType(diagramType),
label: extractRelationshipLabel(description, entity1, entity2)
label: extractRelationshipLabel(description, entity1, entity2),
});
}
}
@ -313,7 +313,7 @@ const getMatchedKeywords = (text: string, diagramType: DiagramType): string[] =>
const generateReasoning = (
bestMatch: { type: DiagramType; confidence: number },
keywords: string[],
entities: string[]
entities: string[],
): string => {
return `Detected ${bestMatch.type} with ${Math.round(bestMatch.confidence * 100)}% confidence. ` +
`Found ${keywords.length} relevant keywords: ${keywords.slice(0, 5).join(', ')}. ` +

View File

@ -1,5 +1,4 @@
import { DiagramType, DiagramData, DiagramElement, DiagramConnection } from '../types/diagram-types.js';
import { DiagramAnalysis } from '../types/diagram-types.js';
import { DiagramType, DiagramData, DiagramElement, DiagramConnection, DiagramAnalysis } from '../types/diagram-types.js';
/**
* Generate intelligent system architecture diagram based on analysis
@ -7,7 +6,7 @@ import { DiagramAnalysis } from '../types/diagram-types.js';
export const generateSmartArchitectureDiagram = (
description: string,
analysis: DiagramAnalysis,
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {}
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {},
): DiagramData => {
const { entities, relationships } = analysis;
const complexity = preferences.complexity || 'detailed';
@ -55,7 +54,7 @@ export const generateSmartArchitectureDiagram = (
`component-${sourceComponent.name}`,
`component-${targetComponent.name}`,
connection.type,
connection.label
connection.label,
);
connectionElements.push(connectionElement);
}
@ -69,8 +68,8 @@ export const generateSmartArchitectureDiagram = (
format: 'drawio' as any,
created: new Date().toISOString(),
modified: new Date().toISOString(),
version: '1.0'
}
version: '1.0',
},
};
};
@ -98,7 +97,7 @@ const extractArchitectureComponents = (entities: string[], description: string):
frontend: ['frontend', 'cliente', 'client', 'ui', 'interfaz', 'react', 'vue', 'angular'],
backend: ['backend', 'servidor', 'server', 'node', 'express', 'spring'],
middleware: ['middleware', 'proxy', 'gateway', 'balanceador', 'load balancer'],
external: ['externo', 'external', 'tercero', 'third party', 'integración', 'integration']
external: ['externo', 'external', 'tercero', 'third party', 'integración', 'integration'],
};
// Extract components from entities
@ -110,7 +109,7 @@ const extractArchitectureComponents = (entities: string[], description: string):
name: cleanComponentName(entity),
type: componentType,
description: extractComponentDescription(entity, description),
technologies
technologies,
});
});
@ -124,7 +123,7 @@ const extractArchitectureComponents = (entities: string[], description: string):
{ name: 'Frontend', type: 'frontend', technologies: ['React'] },
{ name: 'API Gateway', type: 'middleware', technologies: ['Express'] },
{ name: 'Backend Service', type: 'backend', technologies: ['Node.js'] },
{ name: 'Database', type: 'database', technologies: ['PostgreSQL'] }
{ name: 'Database', type: 'database', technologies: ['PostgreSQL'] },
);
}
@ -136,7 +135,7 @@ const extractArchitectureComponents = (entities: string[], description: string):
*/
const inferComponentType = (
componentName: string,
typeMap: Record<string, string[]>
typeMap: Record<string, string[]>,
): 'service' | 'database' | 'api' | 'frontend' | 'backend' | 'middleware' | 'external' => {
const name = componentName.toLowerCase();
@ -157,7 +156,7 @@ const extractTechnologies = (componentName: string, description: string): string
const techKeywords = [
'react', 'vue', 'angular', 'node', 'express', 'spring', 'django',
'mysql', 'postgresql', 'mongodb', 'redis', 'docker', 'kubernetes',
'aws', 'azure', 'gcp', 'nginx', 'apache', 'java', 'python', 'javascript'
'aws', 'azure', 'gcp', 'nginx', 'apache', 'java', 'python', 'javascript',
];
const text = description.toLowerCase();
@ -177,7 +176,7 @@ const extractComponentDescription = (componentName: string, description: string)
// Look for descriptions near the component name
const patterns = [
new RegExp(`${componentName}\\s+(?:es|is|se encarga de|handles?)\\s+([^.]{1,100})`, 'gi'),
new RegExp(`${componentName}\\s*:?\\s*([^.]{1,100})`, 'gi')
new RegExp(`${componentName}\\s*:?\\s*([^.]{1,100})`, 'gi'),
];
for (const pattern of patterns) {
@ -195,7 +194,7 @@ const extractComponentDescription = (componentName: string, description: string)
*/
const extractComponentsFromDescription = (
description: string,
typeMap: Record<string, string[]>
typeMap: Record<string, string[]>,
): Array<{
name: string;
type: 'service' | 'database' | 'api' | 'frontend' | 'backend' | 'middleware' | 'external';
@ -212,7 +211,7 @@ const extractComponentsFromDescription = (
// Look for component patterns
const componentPatterns = [
/(?:componente|component|servicio|service|módulo|module)\s*:?\s*([^,.\n]+)/gi,
/(?:incluye|includes?|tiene|has)\s+(?:un|una|a|an)?\s*([^,.\n]+)/gi
/(?:incluye|includes?|tiene|has)\s+(?:un|una|a|an)?\s*([^,.\n]+)/gi,
];
for (const pattern of componentPatterns) {
@ -226,7 +225,7 @@ const extractComponentsFromDescription = (
components.push({
name,
type,
technologies
technologies,
});
}
}
@ -240,7 +239,7 @@ const extractComponentsFromDescription = (
*/
const extractArchitectureLayers = (
description: string,
components: Array<{ name: string; type: string }>
components: Array<{ name: string; type: string }>,
): Array<{
name: string;
type: 'presentation' | 'business' | 'data' | 'integration';
@ -256,33 +255,33 @@ const extractArchitectureLayers = (
const layerMappings = {
presentation: {
name: 'Capa de Presentación',
types: ['frontend', 'ui', 'client']
types: ['frontend', 'ui', 'client'],
},
business: {
name: 'Capa de Negocio',
types: ['service', 'backend', 'api']
types: ['service', 'backend', 'api'],
},
data: {
name: 'Capa de Datos',
types: ['database', 'storage']
types: ['database', 'storage'],
},
integration: {
name: 'Capa de Integración',
types: ['middleware', 'external', 'gateway']
}
types: ['middleware', 'external', 'gateway'],
},
};
// Organize components into layers
Object.entries(layerMappings).forEach(([layerType, layerConfig]) => {
const layerComponents = components.filter(component =>
layerConfig.types.some(type => component.type.includes(type))
layerConfig.types.some(type => component.type.includes(type)),
);
if (layerComponents.length > 0) {
layers.push({
name: layerConfig.name,
type: layerType as any,
components: layerComponents
components: layerComponents,
});
}
});
@ -293,18 +292,18 @@ const extractArchitectureLayers = (
{
name: 'Capa de Presentación',
type: 'presentation',
components: components.filter(c => c.type === 'frontend')
components: components.filter(c => c.type === 'frontend'),
},
{
name: 'Capa de Negocio',
type: 'business',
components: components.filter(c => ['service', 'backend', 'api'].includes(c.type))
components: components.filter(c => ['service', 'backend', 'api'].includes(c.type)),
},
{
name: 'Capa de Datos',
type: 'data',
components: components.filter(c => c.type === 'database')
}
components: components.filter(c => c.type === 'database'),
},
);
}
@ -317,7 +316,7 @@ const extractArchitectureLayers = (
const extractArchitectureConnections = (
relationships: Array<{ from: string; to: string; type: string; label?: string }>,
description: string,
components: Array<{ name: string; type: string }>
components: Array<{ name: string; type: string }>,
): Array<{ from: string; to: string; type: string; label?: string }> => {
const connections: Array<{ from: string; to: string; type: string; label?: string }> = [];
@ -325,7 +324,7 @@ const extractArchitectureConnections = (
relationships.forEach(rel => {
connections.push({
...rel,
type: inferConnectionType(rel.from, rel.to, components)
type: inferConnectionType(rel.from, rel.to, components),
});
});
@ -340,7 +339,7 @@ const extractArchitectureConnections = (
// Check if connection already exists
const exists = connections.some(conn =>
(conn.from === comp1.name && conn.to === comp2.name) ||
(conn.from === comp2.name && conn.to === comp1.name)
(conn.from === comp2.name && conn.to === comp1.name),
);
if (!exists) {
@ -359,7 +358,7 @@ const extractArchitectureConnections = (
const inferConnectionType = (
from: string,
to: string,
components: Array<{ name: string; type: string }>
components: Array<{ name: string; type: string }>,
): string => {
const fromComp = components.find(c => c.name === from);
const toComp = components.find(c => c.name === to);
@ -381,7 +380,7 @@ const inferConnectionType = (
const inferImplicitConnection = (
comp1: { name: string; type: string },
comp2: { name: string; type: string },
description: string
_description: string,
): { from: string; to: string; type: string; label?: string } | null => {
// Common architecture patterns
const patterns = [
@ -389,7 +388,7 @@ const inferImplicitConnection = (
{ from: 'api', to: 'database', label: 'Query' },
{ from: 'service', to: 'database', label: 'Data Access' },
{ from: 'middleware', to: 'service', label: 'Route' },
{ from: 'frontend', to: 'service', label: 'API Call' }
{ from: 'frontend', to: 'service', label: 'API Call' },
];
for (const pattern of patterns) {
@ -399,7 +398,7 @@ const inferImplicitConnection = (
from: comp1.name,
to: comp2.name,
type: inferConnectionType(comp1.name, comp2.name, [comp1, comp2]),
label: pattern.label
label: pattern.label,
};
}
}
@ -420,7 +419,7 @@ const cleanComponentName = (name: string): string => {
const findComponentInLayers = (
layers: Array<{ components: Array<{ name: string; type: string }> }>,
componentName: string
componentName: string,
): { name: string; type: string } | null => {
for (const layer of layers) {
const component = layer.components.find(c => c.name === componentName);
@ -438,7 +437,7 @@ const createLayer = (
layer: { name: string; type: string; components: Array<{ name: string; type: string }> },
x: number,
y: number,
complexity: 'simple' | 'detailed'
_complexity: 'simple' | 'detailed',
): DiagramElement => {
const width = 800;
const height = 150;
@ -449,7 +448,7 @@ const createLayer = (
label: layer.name,
geometry: { x, y, width, height },
style: 'swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;fillColor=#f8f9fa;strokeColor=#dee2e6;',
properties: { layerType: layer.type, layerName: layer.name }
properties: { layerType: layer.type, layerName: layer.name },
};
};
@ -460,7 +459,7 @@ const createComponent = (
component: { name: string; type: string; description?: string; technologies?: string[] },
x: number,
y: number,
complexity: 'simple' | 'detailed'
complexity: 'simple' | 'detailed',
): DiagramElement => {
const width = complexity === 'detailed' ? 180 : 120;
const height = complexity === 'detailed' ? 100 : 80;
@ -483,7 +482,7 @@ const createComponent = (
api: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;',
service: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;',
middleware: 'rhombus;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;',
external: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;dashed=1;'
external: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;dashed=1;',
};
const style = styleMap[component.type as keyof typeof styleMap] || styleMap.service;
@ -498,8 +497,8 @@ const createComponent = (
componentType: component.type,
componentName: component.name,
technologies: component.technologies || [],
description: component.description || ''
}
description: component.description || '',
},
};
};
@ -510,7 +509,7 @@ const createArchitectureConnection = (
sourceId: string,
targetId: string,
connectionType: string,
label?: string
label?: string,
): DiagramConnection => {
// Choose style based on connection type
const styleMap = {
@ -518,7 +517,7 @@ const createArchitectureConnection = (
data_access: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#82b366;',
service_call: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#6c8ebf;',
proxy: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#d79b00;',
dependency: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#666666;'
dependency: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;strokeColor=#666666;',
};
const style = styleMap[connectionType as keyof typeof styleMap] || styleMap.dependency;
@ -529,6 +528,6 @@ const createArchitectureConnection = (
target: targetId,
label: label || '',
style,
properties: { connectionType }
properties: { connectionType },
};
};

View File

@ -1,5 +1,4 @@
import { DiagramType, DiagramData, DiagramElement, DiagramConnection } from '../types/diagram-types.js';
import { DiagramAnalysis } from '../types/diagram-types.js';
import { DiagramType, DiagramData, DiagramElement, DiagramConnection, DiagramAnalysis } from '../types/diagram-types.js';
/**
* Generate intelligent BPMN diagram based on analysis
@ -7,9 +6,9 @@ import { DiagramAnalysis } from '../types/diagram-types.js';
export const generateSmartBPMNDiagram = (
description: string,
analysis: DiagramAnalysis,
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {}
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {},
): DiagramData => {
const { entities, relationships } = analysis;
const { entities } = analysis;
const complexity = preferences.complexity || 'detailed';
// Identify different types of BPMN elements from entities
@ -22,7 +21,7 @@ export const generateSmartBPMNDiagram = (
const connections: DiagramConnection[] = [];
let currentX = 100;
let currentY = 150;
const currentY = 150;
const spacing = 200;
// Add start event
@ -68,7 +67,7 @@ export const generateSmartBPMNDiagram = (
currentX,
currentY,
spacing,
complexity
complexity,
);
elements.push(...branchResults.elements);
@ -93,8 +92,8 @@ export const generateSmartBPMNDiagram = (
format: 'drawio' as any,
created: new Date().toISOString(),
modified: new Date().toISOString(),
version: '1.0'
}
version: '1.0',
},
};
};
@ -136,7 +135,7 @@ const extractTasks = (entities: string[], description: string): string[] => {
/**
* Extract events from entities and description
*/
const extractEvents = (entities: string[], description: string): string[] => {
const extractEvents = (entities: string[], _description: string): string[] => {
const eventKeywords = ['evento', 'event', 'inicio', 'start', 'fin', 'end', 'trigger', 'disparador'];
const events: string[] = [];
@ -175,7 +174,7 @@ const extractGateways = (entities: string[], description: string): Array<{ name:
/si\s+(.+?)\s+entonces/gi,
/if\s+(.+?)\s+then/gi,
/cuando\s+(.+?)\s+[,.]?/gi,
/en caso de\s+(.+?)\s+[,.]?/gi
/en caso de\s+(.+?)\s+[,.]?/gi,
];
for (const pattern of decisionPatterns) {
@ -196,7 +195,7 @@ const extractGateways = (entities: string[], description: string): Array<{ name:
const buildProcessFlow = (
tasks: string[],
gateways: Array<{ name: string; type: 'exclusive' | 'parallel' | 'inclusive' }>,
events: string[]
_events: string[],
): Array<{ type: string; name: string; gatewayType?: string; branches?: string[][] }> => {
const flow: Array<{ type: string; name: string; gatewayType?: string; branches?: string[][] }> = [];
@ -221,7 +220,7 @@ const buildProcessFlow = (
type: 'gateway',
name: gateway.name,
gatewayType: gateway.type,
branches: branches
branches: branches,
});
gatewayIndex++;
@ -265,7 +264,7 @@ const createStartEvent = (x: number, y: number): DiagramElement => ({
label: 'Inicio',
geometry: { x, y, width: 36, height: 36 },
style: 'ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#d5e8d4;strokeColor=#82b366;',
properties: { eventType: 'start' }
properties: { eventType: 'start' },
});
const createEndEvent = (x: number, y: number): DiagramElement => ({
@ -274,7 +273,7 @@ const createEndEvent = (x: number, y: number): DiagramElement => ({
label: 'Fin',
geometry: { x, y, width: 36, height: 36 },
style: 'ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#f8cecc;strokeColor=#b85450;strokeWidth=3;',
properties: { eventType: 'end' }
properties: { eventType: 'end' },
});
const createTask = (name: string, x: number, y: number, complexity: 'simple' | 'detailed'): DiagramElement => {
@ -287,7 +286,7 @@ const createTask = (name: string, x: number, y: number, complexity: 'simple' | '
label: name,
geometry: { x, y, width, height },
style: 'rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { taskType: 'user' }
properties: { taskType: 'user' },
};
};
@ -297,7 +296,7 @@ const createGateway = (name: string, x: number, y: number, gatewayType: string):
label: name,
geometry: { x, y, width: 50, height: 50 },
style: 'rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;',
properties: { gatewayType }
properties: { gatewayType },
});
const createIntermediateEvent = (name: string, x: number, y: number): DiagramElement => ({
@ -306,7 +305,7 @@ const createIntermediateEvent = (name: string, x: number, y: number): DiagramEle
label: name,
geometry: { x, y, width: 36, height: 36 },
style: 'ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#e1d5e7;strokeColor=#9673a6;',
properties: { eventType: 'intermediate' }
properties: { eventType: 'intermediate' },
});
const createSequenceFlow = (sourceId: string, targetId: string, label?: string): DiagramConnection => ({
@ -315,7 +314,7 @@ const createSequenceFlow = (sourceId: string, targetId: string, label?: string):
target: targetId,
label: label || '',
style: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;',
properties: { flowType: 'sequence' }
properties: { flowType: 'sequence' },
});
/**
@ -327,7 +326,7 @@ const createGatewayBranchElements = (
startX: number,
startY: number,
spacing: number,
complexity: 'simple' | 'detailed'
complexity: 'simple' | 'detailed',
): {
elements: DiagramElement[];
connections: DiagramConnection[];
@ -350,7 +349,7 @@ const createGatewayBranchElements = (
let branchX = startX;
let previousId = gatewayId;
branch.forEach((taskName, taskIndex) => {
branch.forEach((taskName, _taskIndex) => {
branchX += spacing;
const task = createTask(taskName, branchX, branchY, complexity);
elements.push(task);
@ -370,6 +369,6 @@ const createGatewayBranchElements = (
elements,
connections,
nextX: maxX + spacing,
convergenceGatewayId: convergenceGateway.id
convergenceGatewayId: convergenceGateway.id,
};
};

View File

@ -1,5 +1,4 @@
import { DiagramType, DiagramData, DiagramElement, DiagramConnection } from '../types/diagram-types.js';
import { DiagramAnalysis } from '../types/diagram-types.js';
import { DiagramType, DiagramData, DiagramElement, DiagramConnection, DiagramAnalysis } from '../types/diagram-types.js';
/**
* Generate intelligent ER diagram based on analysis
@ -7,7 +6,7 @@ import { DiagramAnalysis } from '../types/diagram-types.js';
export const generateSmartERDiagram = (
description: string,
analysis: DiagramAnalysis,
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {}
preferences: { complexity?: 'simple' | 'detailed'; language?: string } = {},
): DiagramData => {
const { entities, relationships } = analysis;
const complexity = preferences.complexity || 'detailed';
@ -47,7 +46,7 @@ export const generateSmartERDiagram = (
`entity-${sourceEntity.name}`,
`entity-${targetEntity.name}`,
relationship.type,
relationship.label
relationship.label,
);
connections.push(connection);
}
@ -61,8 +60,8 @@ export const generateSmartERDiagram = (
format: 'drawio' as any,
created: new Date().toISOString(),
modified: new Date().toISOString(),
version: '1.0'
}
version: '1.0',
},
};
};
@ -107,7 +106,7 @@ const extractDatabaseEntities = (entities: string[], description: string): Array
const attributes = generateEntityAttributes(entityName, description);
dbEntities.push({
name: entityName,
attributes
attributes,
});
});
@ -119,7 +118,7 @@ const extractDatabaseEntities = (entities: string[], description: string): Array
*/
const generateEntityAttributes = (
entityName: string,
description: string
description: string,
): Array<{ name: string; type: string; isPrimaryKey?: boolean; isForeignKey?: boolean }> => {
const attributes: Array<{ name: string; type: string; isPrimaryKey?: boolean; isForeignKey?: boolean }> = [];
@ -127,7 +126,7 @@ const generateEntityAttributes = (
attributes.push({
name: 'id',
type: 'INT',
isPrimaryKey: true
isPrimaryKey: true,
});
// Generate context-specific attributes based on entity name
@ -141,7 +140,7 @@ const generateEntityAttributes = (
// Add common attributes
attributes.push(
{ name: 'created_at', type: 'TIMESTAMP' },
{ name: 'updated_at', type: 'TIMESTAMP' }
{ name: 'updated_at', type: 'TIMESTAMP' },
);
return attributes.slice(0, 8); // Limit to 8 attributes for readability
@ -156,55 +155,55 @@ const getContextualAttributes = (entityName: string): Array<{ name: string; type
{ name: 'nombre', type: 'VARCHAR(100)' },
{ name: 'email', type: 'VARCHAR(255)' },
{ name: 'password', type: 'VARCHAR(255)' },
{ name: 'telefono', type: 'VARCHAR(20)' }
{ name: 'telefono', type: 'VARCHAR(20)' },
],
user: [
{ name: 'name', type: 'VARCHAR(100)' },
{ name: 'email', type: 'VARCHAR(255)' },
{ name: 'password', type: 'VARCHAR(255)' },
{ name: 'phone', type: 'VARCHAR(20)' }
{ name: 'phone', type: 'VARCHAR(20)' },
],
producto: [
{ name: 'nombre', type: 'VARCHAR(200)' },
{ name: 'descripcion', type: 'TEXT' },
{ name: 'precio', type: 'DECIMAL(10,2)' },
{ name: 'stock', type: 'INT' }
{ name: 'stock', type: 'INT' },
],
product: [
{ name: 'name', type: 'VARCHAR(200)' },
{ name: 'description', type: 'TEXT' },
{ name: 'price', type: 'DECIMAL(10,2)' },
{ name: 'stock', type: 'INT' }
{ name: 'stock', type: 'INT' },
],
pedido: [
{ name: 'numero', type: 'VARCHAR(50)' },
{ name: 'total', type: 'DECIMAL(10,2)' },
{ name: 'estado', type: 'VARCHAR(50)' },
{ name: 'fecha', type: 'DATE' }
{ name: 'fecha', type: 'DATE' },
],
order: [
{ name: 'number', type: 'VARCHAR(50)' },
{ name: 'total', type: 'DECIMAL(10,2)' },
{ name: 'status', type: 'VARCHAR(50)' },
{ name: 'date', type: 'DATE' }
{ name: 'date', type: 'DATE' },
],
cliente: [
{ name: 'nombre', type: 'VARCHAR(100)' },
{ name: 'email', type: 'VARCHAR(255)' },
{ name: 'direccion', type: 'TEXT' },
{ name: 'telefono', type: 'VARCHAR(20)' }
{ name: 'telefono', type: 'VARCHAR(20)' },
],
customer: [
{ name: 'name', type: 'VARCHAR(100)' },
{ name: 'email', type: 'VARCHAR(255)' },
{ name: 'address', type: 'TEXT' },
{ name: 'phone', type: 'VARCHAR(20)' }
]
{ name: 'phone', type: 'VARCHAR(20)' },
],
};
return attributeMap[entityName] || [
{ name: 'name', type: 'VARCHAR(100)' },
{ name: 'description', type: 'TEXT' }
{ name: 'description', type: 'TEXT' },
];
};
@ -213,14 +212,14 @@ const getContextualAttributes = (entityName: string): Array<{ name: string; type
*/
const extractAttributesFromDescription = (
description: string,
entityName: string
entityName: string,
): Array<{ name: string; type: string }> => {
const attributes: Array<{ name: string; type: string }> = [];
// Look for attribute patterns
const attributePatterns = [
/(?:campo|field|atributo|attribute|columna|column)\s*:?\s*([^,.\n]+)/gi,
new RegExp(`${entityName}\\s+(?:tiene|has|incluye|includes?)\\s+([^,.\n]+)`, 'gi')
new RegExp(`${entityName}\\s+(?:tiene|has|incluye|includes?)\\s+([^,.\n]+)`, 'gi'),
];
for (const pattern of attributePatterns) {
@ -271,7 +270,7 @@ const inferAttributeType = (attributeName: string): string => {
const extractDatabaseRelationships = (
relationships: Array<{ from: string; to: string; type: string; label?: string }>,
description: string,
entities: Array<{ name: string; attributes: any[] }>
entities: Array<{ name: string; attributes: any[] }>,
): Array<{ from: string; to: string; type: string; label?: string; cardinality?: string }> => {
const dbRelationships: Array<{ from: string; to: string; type: string; label?: string; cardinality?: string }> = [];
@ -280,7 +279,7 @@ const extractDatabaseRelationships = (
const cardinality = inferCardinality(rel.from, rel.to, description);
dbRelationships.push({
...rel,
cardinality
cardinality,
});
});
@ -293,7 +292,7 @@ const extractDatabaseRelationships = (
// Check if relationship already exists
const existingRel = dbRelationships.find(
rel => (rel.from === entity1.name && rel.to === entity2.name) ||
(rel.from === entity2.name && rel.to === entity1.name)
(rel.from === entity2.name && rel.to === entity1.name),
);
if (!existingRel) {
@ -331,13 +330,13 @@ const inferCardinality = (entity1: string, entity2: string, description: string)
const commonOneToMany = [
['usuario', 'pedido'], ['user', 'order'],
['cliente', 'pedido'], ['customer', 'order'],
['categoria', 'producto'], ['category', 'product']
['categoria', 'producto'], ['category', 'product'],
];
const commonManyToMany = [
['producto', 'categoria'], ['product', 'category'],
['usuario', 'rol'], ['user', 'role'],
['estudiante', 'curso'], ['student', 'course']
['estudiante', 'curso'], ['student', 'course'],
];
for (const [first, second] of commonOneToMany) {
@ -363,7 +362,7 @@ const inferCardinality = (entity1: string, entity2: string, description: string)
const inferImplicitRelationship = (
entity1: string,
entity2: string,
description: string
_description: string,
): { from: string; to: string; type: string; label?: string; cardinality?: string } | null => {
const e1 = entity1.toLowerCase();
const e2 = entity2.toLowerCase();
@ -375,7 +374,7 @@ const inferImplicitRelationship = (
{ entities: ['cliente', 'pedido'], label: 'hace', cardinality: '1:N' },
{ entities: ['customer', 'order'], label: 'makes', cardinality: '1:N' },
{ entities: ['producto', 'categoria'], label: 'pertenece a', cardinality: 'N:M' },
{ entities: ['product', 'category'], label: 'belongs to', cardinality: 'N:M' }
{ entities: ['product', 'category'], label: 'belongs to', cardinality: 'N:M' },
];
for (const pattern of relationshipPatterns) {
@ -387,7 +386,7 @@ const inferImplicitRelationship = (
to: entity2,
type: 'relationship',
label: pattern.label,
cardinality: pattern.cardinality
cardinality: pattern.cardinality,
};
}
}
@ -425,7 +424,7 @@ const createEntity = (
entity: { name: string; attributes: Array<{ name: string; type: string; isPrimaryKey?: boolean; isForeignKey?: boolean }> },
x: number,
y: number,
complexity: 'simple' | 'detailed'
complexity: 'simple' | 'detailed',
): DiagramElement => {
const width = complexity === 'detailed' ? 200 : 150;
const attributeHeight = complexity === 'detailed' ? 20 : 16;
@ -457,7 +456,7 @@ const createEntity = (
label,
geometry: { x, y, width, height },
style: 'swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { entityType: 'table', entityName: entity.name }
properties: { entityType: 'table', entityName: entity.name },
};
};
@ -468,7 +467,7 @@ const createRelationship = (
sourceId: string,
targetId: string,
relationshipType: string,
label?: string
label?: string,
): DiagramConnection => {
return {
id: `rel-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
@ -476,6 +475,6 @@ const createRelationship = (
target: targetId,
label: label || '',
style: 'edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;startArrow=ERmandOne;',
properties: { relationshipType, cardinality: '1:N' }
properties: { relationshipType, cardinality: '1:N' },
};
};

View File

@ -11,7 +11,6 @@ import {
ListToolsRequestSchema,
McpError,
ReadResourceRequestSchema,
InitializeRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
// Import tools and utilities
@ -64,103 +63,103 @@ const createToolDefinitions = () => [
properties: {
name: {
type: 'string',
description: 'Name of the diagram file (without extension)'
description: 'Name of the diagram file (without extension)',
},
type: {
type: 'string',
enum: Object.values(DiagramType),
description: 'Type of diagram to create'
description: 'Type of diagram to create',
},
format: {
type: 'string',
enum: Object.values(DiagramFormat),
default: 'drawio',
description: 'File format for the diagram'
description: 'File format for the diagram',
},
description: {
type: 'string',
description: 'Natural language description of the diagram to generate using AI'
description: 'Natural language description of the diagram to generate using AI',
},
outputPath: {
type: 'string',
description: 'Output directory path (relative to workspace)'
description: 'Output directory path (relative to workspace)',
},
complexity: {
type: 'string',
enum: ['simple', 'detailed'],
default: 'detailed',
description: 'Complexity level of the generated diagram'
description: 'Complexity level of the generated diagram',
},
language: {
type: 'string',
default: 'es',
description: 'Language for diagram labels and text'
description: 'Language for diagram labels and text',
},
// Legacy parameters for backward compatibility
processName: {
type: 'string',
description: 'Name of the BPMN process (legacy)'
description: 'Name of the BPMN process (legacy)',
},
tasks: {
type: 'array',
items: { type: 'string' },
description: 'List of tasks for BPMN process (legacy)'
description: 'List of tasks for BPMN process (legacy)',
},
gatewayType: {
type: 'string',
enum: ['exclusive', 'parallel'],
description: 'Type of gateway for BPMN process (legacy)'
description: 'Type of gateway for BPMN process (legacy)',
},
branches: {
type: 'array',
items: {
type: 'array',
items: { type: 'string' }
items: { type: 'string' },
},
description: 'Branches for BPMN gateway (legacy)'
description: 'Branches for BPMN gateway (legacy)',
},
beforeGateway: {
type: 'array',
items: { type: 'string' },
description: 'Tasks before gateway (legacy)'
description: 'Tasks before gateway (legacy)',
},
afterGateway: {
type: 'array',
items: { type: 'string' },
description: 'Tasks after gateway (legacy)'
description: 'Tasks after gateway (legacy)',
},
classes: {
type: 'array',
items: { type: 'string' },
description: 'List of classes for UML class diagram (legacy)'
description: 'List of classes for UML class diagram (legacy)',
},
entities: {
type: 'array',
items: { type: 'string' },
description: 'List of entities for ER diagram (legacy)'
description: 'List of entities for ER diagram (legacy)',
},
components: {
type: 'array',
items: { type: 'string' },
description: 'List of components for network or architecture diagram (legacy)'
description: 'List of components for network or architecture diagram (legacy)',
},
processes: {
type: 'array',
items: { type: 'string' },
description: 'List of processes for flowchart (legacy)'
}
description: 'List of processes for flowchart (legacy)',
},
},
required: ['name', 'type'],
},
required: ['name', 'type']
}
},
{
name: 'get_diagram_types',
description: 'Get list of supported diagram types with descriptions',
inputSchema: {
type: 'object',
properties: {}
}
}
properties: {},
},
},
];
// Pure function to create resource definitions
@ -169,8 +168,8 @@ const createResourceDefinitions = () => [
uri: 'diagrams://types/supported',
name: 'Supported Diagram Types',
mimeType: 'application/json',
description: 'List of supported diagram types and their descriptions'
}
description: 'List of supported diagram types and their descriptions',
},
];
// Pure function to create tool handlers
@ -179,22 +178,22 @@ const createToolHandlers = (config: ServerConfig): ToolHandlers => ({
if (!validateCreateDiagramInput(args)) {
throw new McpError(
ErrorCode.InvalidParams,
'Invalid create_diagram arguments'
'Invalid create_diagram arguments',
);
}
const result = await createDiagram({
...args,
workspaceRoot: args.workspaceRoot || config.workspaceRoot
workspaceRoot: args.workspaceRoot || config.workspaceRoot,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
text: JSON.stringify(result, null, 2),
},
],
};
},
@ -202,7 +201,7 @@ const createToolHandlers = (config: ServerConfig): ToolHandlers => ({
const types = getSupportedDiagramTypes();
const typesWithDescriptions = types.map(type => ({
type,
description: getDiagramTypeDescription(type)
description: getDiagramTypeDescription(type),
}));
return {
@ -211,21 +210,21 @@ const createToolHandlers = (config: ServerConfig): ToolHandlers => ({
type: 'text',
text: JSON.stringify({
success: true,
supportedTypes: typesWithDescriptions
}, null, 2)
}
]
supportedTypes: typesWithDescriptions,
}, null, 2),
},
],
};
}
},
});
// Pure function to create resource handlers
const createResourceHandlers = (config: ServerConfig): ResourceHandlers => ({
const createResourceHandlers = (_config: ServerConfig): ResourceHandlers => ({
'diagrams://types/supported': async () => {
const types = getSupportedDiagramTypes();
const typesWithDescriptions = types.map(type => ({
type,
description: getDiagramTypeDescription(type)
description: getDiagramTypeDescription(type),
}));
return {
@ -233,18 +232,18 @@ const createResourceHandlers = (config: ServerConfig): ResourceHandlers => ({
{
uri: 'diagrams://types/supported',
mimeType: 'application/json',
text: JSON.stringify(typesWithDescriptions, null, 2)
}
]
text: JSON.stringify(typesWithDescriptions, null, 2),
},
],
};
}
},
});
// Pure function to setup tool request handlers
const setupToolRequestHandlers = (server: Server, toolHandlers: ToolHandlers) => {
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: createToolDefinitions()
tools: createToolDefinitions(),
}));
// Handle tool calls
@ -254,7 +253,7 @@ const setupToolRequestHandlers = (server: Server, toolHandlers: ToolHandlers) =>
if (!handler) {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
`Unknown tool: ${request.params.name}`,
);
}
@ -280,7 +279,7 @@ const setupToolRequestHandlers = (server: Server, toolHandlers: ToolHandlers) =>
const setupResourceRequestHandlers = (server: Server, resourceHandlers: ResourceHandlers) => {
// List available resources
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: createResourceDefinitions()
resources: createResourceDefinitions(),
}));
// Handle resource requests
@ -292,7 +291,7 @@ const setupResourceRequestHandlers = (server: Server, resourceHandlers: Resource
if (!handler) {
throw new McpError(
ErrorCode.InvalidRequest,
`Unknown resource URI: ${uri}`
`Unknown resource URI: ${uri}`,
);
}
@ -300,7 +299,7 @@ const setupResourceRequestHandlers = (server: Server, resourceHandlers: Resource
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Failed to read resource: ${error}`
`Failed to read resource: ${error}`,
);
}
});
@ -323,7 +322,7 @@ const createMCPServer = (config: ServerConfig): Server => {
},
{
capabilities: config.capabilities,
}
},
);
const toolHandlers = createToolHandlers(config);
@ -368,7 +367,7 @@ const isInitializeRequest = (body: any): boolean => {
};
// Pure function to create session transport
const createSessionTransport = (config: ServerConfig): StreamableHTTPServerTransport => {
const createSessionTransport = (_config: ServerConfig): StreamableHTTPServerTransport => {
return new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sessionId) => {
@ -484,8 +483,8 @@ const setupAPIRoutes = (app: express.Application, config: ServerConfig) => {
services: {
mcp: 'operational',
diagramGeneration: 'operational',
sessions: Object.keys(transports).length
}
sessions: Object.keys(transports).length,
},
});
});

View File

@ -1,221 +0,0 @@
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
// Mock Express and MCP modules
jest.mock('express');
jest.mock('@modelcontextprotocol/sdk/server/index.js');
jest.mock('@modelcontextprotocol/sdk/server/streamableHttp.js');
describe('MCP Server Tests', () => {
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
jest.resetAllMocks();
});
describe('Server Configuration', () => {
it('should create server configuration with default values', () => {
// Test basic server configuration creation
const config = {
name: 'drawio-mcp-server',
version: '0.1.0',
workspaceRoot: process.cwd(),
httpPort: 3000,
capabilities: {
resources: {},
tools: {}
}
};
expect(config.name).toBe('drawio-mcp-server');
expect(config.version).toBe('0.1.0');
expect(config.httpPort).toBe(3000);
expect(typeof config.workspaceRoot).toBe('string');
});
it('should create server configuration with custom values', () => {
const customConfig = {
name: 'drawio-mcp-server',
version: '0.1.0',
workspaceRoot: '/custom/workspace',
httpPort: 8080,
capabilities: {
resources: {},
tools: {}
}
};
expect(customConfig.workspaceRoot).toBe('/custom/workspace');
expect(customConfig.httpPort).toBe(8080);
});
});
describe('Tool Definitions', () => {
it('should create correct tool definitions', () => {
const toolDefinitions = [
{
name: 'create_diagram',
description: 'Create a new diagram of specified type (BPMN, UML, ER, Network, Architecture, etc.) with AI-powered generation',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the diagram file (without extension)'
},
type: {
type: 'string',
description: 'Type of diagram to create'
}
},
required: ['name', 'type']
}
},
{
name: 'get_diagram_types',
description: 'Get list of supported diagram types with descriptions',
inputSchema: {
type: 'object',
properties: {}
}
}
];
expect(toolDefinitions).toHaveLength(2);
expect(toolDefinitions[0].name).toBe('create_diagram');
expect(toolDefinitions[1].name).toBe('get_diagram_types');
// Check required fields
expect(toolDefinitions[0].inputSchema.required).toContain('name');
expect(toolDefinitions[0].inputSchema.required).toContain('type');
});
});
describe('Resource Definitions', () => {
it('should create correct resource definitions', () => {
const resourceDefinitions = [
{
uri: 'diagrams://types/supported',
name: 'Supported Diagram Types',
mimeType: 'application/json',
description: 'List of supported diagram types and their descriptions'
}
];
expect(resourceDefinitions).toHaveLength(1);
expect(resourceDefinitions[0].uri).toBe('diagrams://types/supported');
expect(resourceDefinitions[0].mimeType).toBe('application/json');
});
});
describe('HTTP Endpoints', () => {
it('should define correct HTTP endpoints', () => {
const endpoints = [
{ method: 'POST', path: '/mcp', description: 'Client-to-server MCP communication' },
{ method: 'GET', path: '/mcp', description: 'Server-to-client notifications via SSE' },
{ method: 'DELETE', path: '/mcp', description: 'Session termination' },
{ method: 'GET', path: '/health', description: 'Health check' },
{ method: 'GET', path: '/', description: 'API documentation' }
];
expect(endpoints).toHaveLength(5);
const postEndpoint = endpoints.find(e => e.method === 'POST' && e.path === '/mcp');
expect(postEndpoint).toBeDefined();
const healthEndpoint = endpoints.find(e => e.method === 'GET' && e.path === '/health');
expect(healthEndpoint).toBeDefined();
});
});
describe('Session Management', () => {
it('should handle session creation', () => {
const sessionId = 'test-session-123';
const sessions: Record<string, any> = {};
// Simulate session creation
sessions[sessionId] = {
id: sessionId,
created: new Date().toISOString(),
active: true
};
expect(sessions[sessionId]).toBeDefined();
expect(sessions[sessionId].id).toBe(sessionId);
expect(sessions[sessionId].active).toBe(true);
});
it('should handle session cleanup', () => {
const sessionId = 'test-session-123';
const sessions: Record<string, any> = {
[sessionId]: {
id: sessionId,
created: new Date().toISOString(),
active: true
}
};
// Simulate session cleanup
delete sessions[sessionId];
expect(sessions[sessionId]).toBeUndefined();
});
});
describe('Error Handling', () => {
it('should handle invalid MCP requests', () => {
const invalidRequest = {
jsonrpc: '2.0',
method: 'invalid_method',
id: 1
};
const errorResponse = {
jsonrpc: '2.0',
error: {
code: -32601,
message: 'Method not found'
},
id: 1
};
expect(errorResponse.error.code).toBe(-32601);
expect(errorResponse.error.message).toBe('Method not found');
});
it('should handle missing session ID', () => {
const errorResponse = {
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: No valid session ID provided'
},
id: null
};
expect(errorResponse.error.code).toBe(-32000);
expect(errorResponse.error.message).toContain('session ID');
});
});
describe('Health Check', () => {
it('should return correct health status', () => {
const healthResponse = {
status: 'healthy',
timestamp: new Date().toISOString(),
version: '0.1.0',
services: {
mcp: 'operational',
diagramGeneration: 'operational',
sessions: 0
}
};
expect(healthResponse.status).toBe('healthy');
expect(healthResponse.services.mcp).toBe('operational');
expect(healthResponse.services.diagramGeneration).toBe('operational');
expect(typeof healthResponse.services.sessions).toBe('number');
});
});
});

View File

@ -1,44 +1,6 @@
// Test setup file for Jest
import { beforeEach, afterEach } from '@jest/globals';
// Type declarations for global test utilities
declare global {
var createMockDiagramData: () => any;
var createMockFileConfig: () => any;
var createMockVSCodeConfig: () => any;
}
// Global test utilities
(global as any).createMockDiagramData = () => ({
elements: [
{
id: 'element-1',
type: 'rectangle',
label: 'Test Element',
geometry: { x: 100, y: 100, width: 200, height: 100 },
style: 'rounded=0;whiteSpace=wrap;html=1;',
properties: {}
}
],
connections: [],
metadata: {
type: 'flowchart',
format: 'drawio',
created: '2025-01-01T00:00:00.000Z',
modified: '2025-01-01T00:00:00.000Z',
version: '1.0'
}
});
(global as any).createMockFileConfig = () => ({
workspaceRoot: '/test/workspace'
});
(global as any).createMockVSCodeConfig = () => ({
workspaceRoot: '/test/workspace',
extensionId: 'hediet.vscode-drawio'
});
// Setup test environment
beforeEach(() => {
// Clear any previous test state

View File

@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals
import { createDiagram, validateCreateDiagramInput, getSupportedDiagramTypes, getDiagramTypeDescription } from '../../tools/create-diagram.js';
import { DiagramType, DiagramFormat } from '../../types/diagram-types.js';
import * as fs from 'fs';
import * as path from 'path';
// Mock file system operations
jest.mock('fs', () => ({

View File

@ -1,5 +1,5 @@
import { DiagramType, DiagramFormat, DiagramData } from '../types/diagram-types.js';
import { convertToDrawioXML, createDiagramFile, getDefaultStyle, generateId } from '../utils/drawio-converter.js';
import { createDiagramFile } from '../utils/drawio-converter.js';
import { analyzeDiagramDescription } from '../ai/diagram-analyzer.js';
import { generateSmartBPMNDiagram } from '../generators/smart-bpmn-generator.js';
import { generateSmartERDiagram } from '../generators/smart-er-generator.js';
@ -45,26 +45,26 @@ const createSuccessResult = (
content: string,
diagramType: DiagramType,
format: DiagramFormat,
name: string
name: string,
): CreateDiagramResult => ({
success: true,
filePath,
content,
message: `Successfully created ${diagramType} diagram: ${name}`,
diagramType,
format
format,
});
// Pure function to create error result
const createErrorResult = (
error: unknown,
diagramType: DiagramType,
format: DiagramFormat
format: DiagramFormat,
): CreateDiagramResult => ({
success: false,
message: `Failed to create diagram: ${error instanceof Error ? error.message : String(error)}`,
diagramType,
format
format,
});
// Pure function to ensure directory exists
@ -93,7 +93,7 @@ const generateAIDiagram = async (input: CreateDiagramInput): Promise<DiagramData
// Generate diagram based on type
const preferences = {
complexity: input.complexity || 'detailed',
language: input.language || 'es'
language: input.language || 'es',
};
switch (input.type) {
@ -134,10 +134,10 @@ const generateBasicDiagram = (input: CreateDiagramInput): DiagramData => {
x: 100,
y: 100,
width: 200,
height: 100
height: 100,
},
style: 'rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: {}
properties: {},
}],
connections: [],
metadata: {
@ -145,8 +145,8 @@ const generateBasicDiagram = (input: CreateDiagramInput): DiagramData => {
format: input.format || DiagramFormat.DRAWIO,
created: new Date().toISOString(),
modified: new Date().toISOString(),
version: '1.0'
}
version: '1.0',
},
};
};
@ -167,10 +167,10 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
x: 100,
y: 100 + (index * 120),
width: 120,
height: 80
height: 80,
},
style: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { isTask: true }
properties: { isTask: true },
});
if (index > 0) {
@ -180,7 +180,7 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
target: `task-${index}`,
label: '',
style: 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;',
properties: {}
properties: {},
});
}
});
@ -195,10 +195,10 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
x: 100 + (index * 200),
y: 100,
width: 120,
height: 80
height: 80,
},
style: 'whiteSpace=wrap;html=1;align=center;fillColor=#e1d5e7;strokeColor=#9673a6;',
properties: { isEntity: true }
properties: { isEntity: true },
});
});
} else if (input.classes && input.classes.length > 0) {
@ -212,10 +212,10 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
x: 100 + (index * 200),
y: 100,
width: 160,
height: 120
height: 120,
},
style: 'swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { isClass: true }
properties: { isClass: true },
});
});
} else if (input.components && input.components.length > 0) {
@ -229,10 +229,10 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
x: 100 + (index % 2) * 300,
y: 100 + Math.floor(index / 2) * 150,
width: 200,
height: 100
height: 100,
},
style: 'rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;',
properties: { isComponent: true }
properties: { isComponent: true },
});
});
} else {
@ -248,8 +248,8 @@ const generateLegacyDiagram = (input: CreateDiagramInput): DiagramData => {
format: input.format || DiagramFormat.DRAWIO,
created: new Date().toISOString(),
modified: new Date().toISOString(),
version: '1.0'
}
version: '1.0',
},
};
};
@ -333,7 +333,7 @@ const getDiagramTypeDescriptions = (): Readonly<Record<DiagramType, string>> =>
[DiagramType.ORGCHART]: 'Organizational chart showing hierarchy',
[DiagramType.MINDMAP]: 'Mind map diagram for brainstorming and organizing ideas',
[DiagramType.WIREFRAME]: 'Wireframe diagram for UI/UX design',
[DiagramType.GANTT]: 'Gantt chart for project management and scheduling'
[DiagramType.GANTT]: 'Gantt chart for project management and scheduling',
});
// Pure function to get description for a specific diagram type

View File

@ -13,13 +13,13 @@ export const convertToDrawioXML = (diagramData: DiagramData): string => {
agent: 'Cline MCP Server',
version: '24.7.17',
etag: generateEtag(),
type: 'device'
type: 'device',
};
// Create diagram structure
const diagram = {
id: generateId(),
name: `${metadata.type}-diagram`
name: `${metadata.type}-diagram`,
};
// Create mxGraphModel
@ -38,7 +38,7 @@ export const convertToDrawioXML = (diagramData: DiagramData): string => {
pageWidth: '827',
pageHeight: '1169',
math: '0',
shadow: '0'
shadow: '0',
};
// Create root cell
@ -68,7 +68,7 @@ export const convertToDrawioXML = (diagramData: DiagramData): string => {
* Convert diagram element to mxCell
*/
const convertElementToMxCell = (element: DiagramElement, id: number): any => {
const { geometry, style, label, properties } = element;
const { geometry, style, label } = element;
return {
id: id.toString(),
@ -81,8 +81,8 @@ const convertElementToMxCell = (element: DiagramElement, id: number): any => {
y: geometry?.y?.toString() || '0',
width: geometry?.width?.toString() || '120',
height: geometry?.height?.toString() || '80',
as: 'geometry'
}
as: 'geometry',
},
};
};
@ -90,7 +90,7 @@ const convertElementToMxCell = (element: DiagramElement, id: number): any => {
* Convert diagram connection to mxCell
*/
const convertConnectionToMxCell = (connection: DiagramConnection, id: number): any => {
const { style, label, source, target, properties } = connection;
const { style, label, source, target } = connection;
return {
id: id.toString(),
@ -102,8 +102,8 @@ const convertConnectionToMxCell = (connection: DiagramConnection, id: number): a
target: findElementIdByName(target),
geometry: {
relative: '1',
as: 'geometry'
}
as: 'geometry',
},
};
};
@ -123,7 +123,7 @@ const buildDrawioXML = (
mxfile: any,
diagram: any,
mxGraphModel: any,
mxCells: any[]
mxCells: any[],
): string => {
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
xml += `<mxfile host="${mxfile.host}" modified="${mxfile.modified}" agent="${mxfile.agent}" version="${mxfile.version}" etag="${mxfile.etag}" type="${mxfile.type}">\n`;
@ -181,7 +181,7 @@ const buildMxCellXML = (cell: any): string => {
*/
export const convertDiagramToFormat = (
diagramData: DiagramData,
format: DiagramFormat
format: DiagramFormat,
): string => {
switch (format) {
case DiagramFormat.DRAWIO:
@ -209,8 +209,8 @@ const convertToDrawioSVG = (diagramData: DiagramData): string => {
// Calculate approximate SVG dimensions based on elements
const { width, height } = calculateDiagramDimensions(diagramData.elements);
let svg = `<?xml version="1.0" encoding="UTF-8"?>\n`;
svg += `<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n`;
let svg = '<?xml version="1.0" encoding="UTF-8"?>\n';
svg += '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n';
svg += `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="${width}px" height="${height}px" viewBox="-0.5 -0.5 ${width} ${height}" content="${encodedXML}">\n`;
svg += ' <defs/>\n';
svg += ' <g>\n';
@ -261,7 +261,7 @@ const calculateDiagramDimensions = (elements: DiagramElement[]): { width: number
return {
width: Math.max(maxX + 50, 800), // Add padding and minimum width
height: Math.max(maxY + 50, 600) // Add padding and minimum height
height: Math.max(maxY + 50, 600), // Add padding and minimum height
};
};
@ -367,7 +367,7 @@ export const getDefaultStyle = (elementType: string): string => {
'rectangle': 'rounded=0;whiteSpace=wrap;html=1;',
'ellipse': 'ellipse;whiteSpace=wrap;html=1;',
'diamond': 'rhombus;whiteSpace=wrap;html=1;',
'triangle': 'triangle;whiteSpace=wrap;html=1;'
'triangle': 'triangle;whiteSpace=wrap;html=1;',
};
return styleMap[elementType] || styleMap['rectangle'];
@ -400,7 +400,7 @@ const extractStrokeColor = (style?: string): string | null => {
export const createDiagramFile = (
diagramData: DiagramData,
format: DiagramFormat,
filename?: string
filename?: string,
): { content: string; filename: string; mimeType: string } => {
const content = convertDiagramToFormat(diagramData, format);
const baseFilename = filename || `diagram-${Date.now()}`;
@ -411,28 +411,28 @@ export const createDiagramFile = (
return {
content,
filename: `${baseFilename}.drawio`,
mimeType: 'application/xml'
mimeType: 'application/xml',
};
case DiagramFormat.DRAWIO_SVG:
return {
content,
filename: `${baseFilename}.svg`,
mimeType: 'image/svg+xml'
mimeType: 'image/svg+xml',
};
case DiagramFormat.DRAWIO_PNG:
return {
content,
filename: `${baseFilename}.png`,
mimeType: 'image/png'
mimeType: 'image/png',
};
default:
return {
content,
filename: `${baseFilename}.drawio`,
mimeType: 'application/xml'
mimeType: 'application/xml',
};
}
};
@ -442,15 +442,15 @@ export const createDiagramFile = (
*/
export const streamDiagramContent = (
diagramData: DiagramData,
format: DiagramFormat = DiagramFormat.DRAWIO
format: DiagramFormat = DiagramFormat.DRAWIO,
): ReadableStream<Uint8Array> => {
const { content, mimeType } = createDiagramFile(diagramData, format);
const { content } = createDiagramFile(diagramData, format);
return new ReadableStream({
start(controller) {
const encoder = new TextEncoder();
controller.enqueue(encoder.encode(content));
controller.close();
}
},
});
};