import WorkflowValidator from '../resources/WorkflowValidator.js';
/**
* Validateur pour la documentation des workflows n8n
*/
class DocumentationValidator implements WorkflowValidator {
/**
* Valide la documentation d'un workflow n8n
* @param workflow Les données du workflow à valider
* @param strictness Le niveau de rigueur de la validation
* @returns Résultat de la validation avec les problèmes détectés
*/
validate(workflow: any, strictness: 'low' | 'medium' | 'high') {
const issues: Array<{
message: string;
recommendation: string;
severity: 'low' | 'medium' | 'high';
location?: string;
}> = [];
// Vérifier la documentation du workflow
this.checkWorkflowDocumentation(workflow, issues, strictness);
// Vérifier la documentation des nœuds
if (workflow.nodes && Array.isArray(workflow.nodes)) {
workflow.nodes.forEach((node: any) => {
this.checkNodeDocumentation(node, issues, strictness);
});
}
return {
valid: issues.length === 0,
issues,
};
}
/**
* Vérifie la documentation au niveau du workflow
*/
private checkWorkflowDocumentation(workflow: any, issues: any[], strictness: string): void {
// Vérifier si le workflow a un nom
if (!workflow.name || workflow.name.trim() === '') {
issues.push({
message: 'Le workflow n\'a pas de nom',
recommendation: 'Donnez un nom descriptif au workflow',
severity: 'high',
});
} else if (workflow.name.length < 5 && strictness !== 'low') {
issues.push({
message: 'Le nom du workflow est trop court',
recommendation: 'Utilisez un nom plus descriptif pour le workflow',
severity: 'medium',
});
}
// Vérifier si le workflow a une description
if (!workflow.description || workflow.description.trim() === '') {
issues.push({
message: 'Le workflow n\'a pas de description',
recommendation: 'Ajoutez une description qui explique le but et le fonctionnement du workflow',
severity: strictness === 'low' ? 'low' : 'medium',
});
} else {
// Vérifier la qualité de la description
this.checkDescriptionQuality(workflow.description, 'workflow', issues, strictness);
}
// Vérifier les tags
if (!workflow.tags || !Array.isArray(workflow.tags) || workflow.tags.length === 0) {
issues.push({
message: 'Le workflow n\'a pas de tags',
recommendation: 'Ajoutez des tags pour catégoriser le workflow et faciliter sa recherche',
severity: strictness === 'high' ? 'medium' : 'low',
});
}
// Vérifier les métadonnées supplémentaires en fonction du niveau de rigueur
if (strictness === 'high') {
// Vérifier si le workflow a un propriétaire ou un responsable
if (!workflow.owner && !workflow.createdBy) {
issues.push({
message: 'Le workflow n\'a pas d\'information sur le propriétaire ou le créateur',
recommendation: 'Ajoutez des informations sur le propriétaire ou le responsable du workflow',
severity: 'low',
});
}
// Vérifier si le workflow a une date de dernière mise à jour
if (!workflow.updatedAt) {
issues.push({
message: 'Le workflow n\'a pas d\'information sur sa dernière mise à jour',
recommendation: 'Ajoutez une date de dernière mise à jour pour suivre l\'évolution du workflow',
severity: 'low',
});
}
}
}
/**
* Vérifie la documentation au niveau des nœuds
*/
private checkNodeDocumentation(node: any, issues: any[], strictness: string): void {
// Vérifier si le nœud a un nom descriptif
if (!node.name || node.name.trim() === '') {
issues.push({
message: 'Un nœud n\'a pas de nom',
recommendation: 'Donnez un nom à tous les nœuds du workflow',
severity: 'high',
location: node.id,
});
} else if (node.name === node.type || this.isDefaultNodeName(node.name, node.type)) {
issues.push({
message: `Le nœud "${node.name}" utilise un nom par défaut`,
recommendation: 'Renommez les nœuds avec des noms descriptifs qui expliquent leur fonction dans le workflow',
severity: strictness === 'low' ? 'low' : 'medium',
location: node.id,
});
}
// Vérifier les notes du nœud
if ((!node.notes || node.notes.trim() === '') && this.isComplexNode(node)) {
issues.push({
message: `Le nœud complexe "${node.name}" n'a pas de notes explicatives`,
recommendation: 'Ajoutez des notes aux nœuds complexes pour expliquer leur configuration et leur rôle',
severity: strictness === 'low' ? 'low' : 'medium',
location: node.id,
});
} else if (node.notes && node.notes.trim() !== '') {
// Vérifier la qualité des notes
this.checkDescriptionQuality(node.notes, `nœud "${node.name}"`, issues, strictness);
}
}
/**
* Vérifie si un nom de nœud est un nom par défaut
*/
private isDefaultNodeName(name: string, type: string): boolean {
if (!name || !type) return false;
// Extraire le nom de base du type (après le dernier point)
const baseType = type.split('.').pop() || '';
// Vérifier si le nom contient le type de base
return name.toLowerCase().includes(baseType.toLowerCase());
}
/**
* Vérifie si un nœud est considéré comme complexe et nécessite une documentation
*/
private isComplexNode(node: any): boolean {
// Les nœuds considérés comme complexes et nécessitant une documentation
const complexNodeTypes = [
'function',
'code',
'switch',
'if',
'split',
'merge',
'http',
'webhook',
'api',
'database',
'execute',
];
// Vérifier si le type du nœud contient l'un des types complexes
return complexNodeTypes.some(type =>
(node.type || '').toLowerCase().includes(type.toLowerCase())
);
}
/**
* Vérifie la qualité d'une description
*/
private checkDescriptionQuality(description: string, context: string, issues: any[], strictness: string): void {
if (!description) return;
// Vérifier la longueur de la description
if (description.length < 10) {
issues.push({
message: `La description du ${context} est trop courte`,
recommendation: 'Fournissez une description plus détaillée',
severity: 'low',
});
}
// Vérifier si la description contient des informations utiles (pour strictness medium et high)
if (strictness !== 'low') {
// Vérifier si la description mentionne le but ou la fonction
const hasPurpose = /but|objectif|fonction|permet|sert à|utilise/i.test(description);
if (!hasPurpose) {
issues.push({
message: `La description du ${context} ne mentionne pas clairement son objectif`,
recommendation: 'Incluez l\'objectif ou la fonction dans la description',
severity: 'low',
});
}
// Pour strictness high, vérifier si la description est complète
if (strictness === 'high' && description.length < 50) {
issues.push({
message: `La description du ${context} pourrait être plus détaillée`,
recommendation: 'Fournissez une description plus complète incluant l\'objectif, les entrées attendues et les sorties produites',
severity: 'low',
});
}
}
}
}
export default DocumentationValidator;