kira-crud
Version:
Intelligent CRUD Generator for Laravel and Angular
982 lines (873 loc) • 32.8 kB
JavaScript
/**
* Model Configuration Wizard
* Interactive wizard for creating model configurations
*/
const inquirer = require('inquirer');
const chalk = require('chalk');
const ora = require('ora');
const fs = require('fs').promises;
const path = require('path');
const yaml = require('js-yaml');
const { buildConfigObject, saveConfigFile } = require('../utils/config-builder');
// Data type choices for model fields
const dataTypeChoices = [
{ name: 'String', value: 'string' },
{ name: 'Integer', value: 'integer' },
{ name: 'Decimal', value: 'decimal' },
{ name: 'Boolean', value: 'boolean' },
{ name: 'Date', value: 'date' },
{ name: 'DateTime', value: 'datetime' },
{ name: 'Text', value: 'text' },
{ name: 'JSON', value: 'json' },
{ name: 'Enum', value: 'enum' }
];
// Validation type choices for model fields
const validationChoices = [
{ name: 'Required', value: 'required' },
{ name: 'Email', value: 'email' },
{ name: 'Min Length', value: 'min' },
{ name: 'Max Length', value: 'max' },
{ name: 'Numeric', value: 'numeric' },
{ name: 'Unique', value: 'unique' },
{ name: 'Regex Pattern', value: 'regex' },
{ name: 'Date Format', value: 'date_format' }
];
// Relationship type choices
const relationshipChoices = [
{ name: 'Belongs To (Many-to-One)', value: 'belongsTo' },
{ name: 'Has Many (One-to-Many)', value: 'hasMany' },
{ name: 'Belongs To Many (Many-to-Many)', value: 'belongsToMany' },
{ name: 'Has One (One-to-One)', value: 'hasOne' },
{ name: 'Morph To (Polymorphic)', value: 'morphTo' },
{ name: 'Morph Many (Polymorphic)', value: 'morphMany' }
];
/**
* Configuration de l'assistant avec état
*/
const wizardState = {
modelInfo: null,
fields: [],
relationships: [],
history: [],
currentStep: 'modelInfo'
};
/**
* Enregistre l'état actuel dans l'historique
*/
function saveStateToHistory() {
// Créer une copie profonde de l'état actuel
const stateCopy = JSON.parse(JSON.stringify(wizardState));
// Supprimer l'historique pour éviter la redondance
delete stateCopy.history;
// Enregistrer dans l'historique
wizardState.history.push(stateCopy);
}
/**
* Restaure l'état précédent depuis l'historique
* @returns {boolean} Vrai si la restauration a réussi, faux sinon
*/
function restorePreviousState() {
if (wizardState.history.length === 0) {
return false;
}
// Récupérer le dernier état de l'historique
const previousState = wizardState.history.pop();
// Restaurer les propriétés
wizardState.modelInfo = previousState.modelInfo;
wizardState.fields = previousState.fields;
wizardState.relationships = previousState.relationships;
wizardState.currentStep = previousState.currentStep;
return true;
}
/**
* Run the model configuration wizard
* @returns {Object} The complete model configuration
*/
async function runModelWizard() {
console.log(chalk.blue.bold('\n📝 Model Configuration Wizard\n'));
// Initialiser l'état du wizard
wizardState.modelInfo = null;
wizardState.fields = [];
wizardState.relationships = [];
wizardState.history = [];
wizardState.currentStep = 'modelInfo';
// Étape 1: Informations de base du modèle
await collectModelInfo();
// Étape 2: Configuration des champs
await collectFields();
// Étape 3: Configuration des relations
await collectRelationships();
// Étape 4: Configuration des options d'UI
await collectUIOptions();
// Étape 5: Enregistrement de la configuration
return await saveConfiguration();
}
/**
* Collecte les informations de base du modèle
*/
async function collectModelInfo() {
console.log(chalk.green('\n✨ Étape 1: Informations de base du modèle\n'));
// Si des informations de modèle existent déjà, les utiliser comme valeurs par défaut
const defaults = wizardState.modelInfo || {};
// Enregistrer l'état actuel avant de collecter de nouvelles informations
saveStateToHistory();
// Collecter les informations de base du modèle
const modelInfoPrompt = [
{
type: 'input',
name: 'name',
message: 'Model name (PascalCase):',
default: defaults.name || '',
validate: input => input && input.length > 0 ? true : 'Model name is required'
},
{
type: 'input',
name: 'tableName',
message: 'Database table name (snake_case):',
default: (input) => defaults.tableName || input.name.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase() + 's'
},
{
type: 'input',
name: 'displayName',
message: 'Human-readable display name:',
default: (input) => defaults.displayName || input.name
},
{
type: 'input',
name: 'description',
message: 'Model description:',
default: defaults.description || ''
},
{
type: 'list',
name: 'architecture',
message: 'Backend architecture:',
choices: [
{ name: 'Advanced (Repository, Service, etc.)', value: 'advanced' },
{ name: 'Classic (Controller, Model only)', value: 'classic' }
],
default: defaults.architecture || 'advanced'
},
{
type: 'list',
name: 'action',
message: 'What would you like to do next?',
choices: [
{ name: 'Continue to next step', value: 'continue' },
{ name: 'Cancel and go back', value: 'back' }
]
}
];
const modelInfo = await inquirer.prompt(modelInfoPrompt);
// Gérer l'action de l'utilisateur
if (modelInfo.action === 'back') {
// Restaurer l'état précédent
if (restorePreviousState()) {
// Si on peut revenir en arrière, recommencer cette étape
return await collectModelInfo();
} else {
// Sinon, quitter l'assistant
throw new Error('Configuration cancelled');
}
}
// Supprimer la propriété 'action' avant de sauvegarder
delete modelInfo.action;
// Mettre à jour l'état avec les nouvelles informations
wizardState.modelInfo = modelInfo;
wizardState.currentStep = 'fields';
}
/**
* Collecte les informations sur les champs du modèle
*/
async function collectFields() {
console.log(chalk.green('\n✨ Étape 2: Configuration des champs du modèle\n'));
// Enregistrer l'état actuel avant de modifier les champs
saveStateToHistory();
let addMoreFields = true;
let editMode = false;
let editIndex = -1;
while (addMoreFields) {
// Afficher les champs existants
if (wizardState.fields.length > 0) {
console.log(chalk.yellow('\nChamps déjà configurés:'));
wizardState.fields.forEach((field, index) => {
console.log(`${index + 1}. ${chalk.cyan(field.name)} (${chalk.green(field.type)})${field.required ? chalk.red(' *required') : ''}`);
});
console.log(); // Ligne vide pour espacement
}
// Demander à l'utilisateur ce qu'il veut faire
const { action } = await inquirer.prompt({
type: 'list',
name: 'action',
message: 'Que souhaitez-vous faire?',
choices: [
...(wizardState.fields.length > 0 ? [{ name: 'Continuer à l\'étape suivante', value: 'continue' }] : []),
{ name: 'Ajouter un nouveau champ', value: 'add' },
...(wizardState.fields.length > 0 ? [
{ name: 'Modifier un champ existant', value: 'edit' },
{ name: 'Supprimer un champ', value: 'delete' }
] : []),
{ name: 'Annuler et revenir à l\'étape précédente', value: 'back' }
]
});
// Gérer l'action de l'utilisateur
if (action === 'continue') {
// Passer à l'étape suivante
wizardState.currentStep = 'relationships';
addMoreFields = false;
continue;
} else if (action === 'back') {
// Revenir à l'étape précédente
if (restorePreviousState()) {
return;
} else {
// Si pas d'état précédent, revenir à l'étape des informations du modèle
wizardState.currentStep = 'modelInfo';
await collectModelInfo();
return;
}
} else if (action === 'edit') {
// Sélectionner un champ à modifier
const { fieldIndex } = await inquirer.prompt({
type: 'list',
name: 'fieldIndex',
message: 'Quel champ souhaitez-vous modifier?',
choices: wizardState.fields.map((field, index) => ({
name: `${index + 1}. ${field.name} (${field.type})`,
value: index
}))
});
editMode = true;
editIndex = fieldIndex;
} else if (action === 'delete') {
// Sélectionner un champ à supprimer
const { fieldIndex } = await inquirer.prompt({
type: 'list',
name: 'fieldIndex',
message: 'Quel champ souhaitez-vous supprimer?',
choices: wizardState.fields.map((field, index) => ({
name: `${index + 1}. ${field.name} (${field.type})`,
value: index
}))
});
// Confirmation de suppression
const { confirmDelete } = await inquirer.prompt({
type: 'confirm',
name: 'confirmDelete',
message: `Êtes-vous sûr de vouloir supprimer le champ "${wizardState.fields[fieldIndex].name}"?`,
default: false
});
if (confirmDelete) {
wizardState.fields.splice(fieldIndex, 1);
console.log(chalk.green('\nChamp supprimé avec succès!'));
}
continue;
}
// Valeurs par défaut pour le champ en mode édition
const defaults = editMode ? wizardState.fields[editIndex] : {};
// Récupérer les informations du champ
const fieldPrompts = [
{
type: 'input',
name: 'name',
message: 'Nom du champ (camelCase):',
default: defaults.name || '',
validate: input => input && input.length > 0 ? true : 'Le nom du champ est requis'
},
{
type: 'list',
name: 'type',
message: 'Type du champ:',
choices: dataTypeChoices,
default: defaults.type ? dataTypeChoices.findIndex(c => c.value === defaults.type) : 0
},
{
type: 'input',
name: 'label',
message: 'Label d\'affichage:',
default: (input) => defaults.label || (input.name ? input.name.charAt(0).toUpperCase() + input.name.slice(1).replace(/([A-Z])/g, ' $1') : '')
},
{
type: 'confirm',
name: 'nullable',
message: 'Autoriser les valeurs nulles?',
default: defaults.nullable !== undefined ? defaults.nullable : false
},
{
type: 'confirm',
name: 'required',
message: 'Ce champ est-il requis?',
default: (input) => defaults.required !== undefined ? defaults.required : !input.nullable
},
{
type: 'checkbox',
name: 'validations',
message: 'Règles de validation:',
choices: validationChoices,
default: defaults.validations || []
}
];
const field = await inquirer.prompt(fieldPrompts);
// Gérer les règles de validation spécifiques
if (field.validations.length > 0) {
field.validationRules = {};
for (const validation of field.validations) {
switch (validation) {
case 'min':
const { minValue } = await inquirer.prompt({
type: 'input',
name: 'minValue',
message: `Valeur minimum ${field.type === 'string' ? 'de longueur' : ''} pour ${field.name}:`,
default: defaults.validationRules?.min,
validate: input => !isNaN(input) ? true : 'Veuillez entrer un nombre',
filter: input => parseInt(input)
});
field.validationRules.min = minValue;
break;
case 'max':
const { maxValue } = await inquirer.prompt({
type: 'input',
name: 'maxValue',
message: `Valeur maximum ${field.type === 'string' ? 'de longueur' : ''} pour ${field.name}:`,
default: defaults.validationRules?.max,
validate: input => !isNaN(input) ? true : 'Veuillez entrer un nombre',
filter: input => parseInt(input)
});
field.validationRules.max = maxValue;
break;
case 'regex':
const { pattern } = await inquirer.prompt({
type: 'input',
name: 'pattern',
message: `Expression régulière pour ${field.name}:`,
default: defaults.validationRules?.regex,
validate: input => input && input.length > 0 ? true : 'L\'expression régulière est requise'
});
field.validationRules.regex = pattern;
break;
default:
field.validationRules[validation] = true;
}
}
}
// Ajouter des options spécifiques pour le type enum
if (field.type === 'enum') {
const { enumValues } = await inquirer.prompt({
type: 'input',
name: 'enumValues',
message: 'Valeurs de l\'enum (séparées par des virgules):',
default: defaults.enumValues ? defaults.enumValues.join(', ') : '',
validate: input => input && input.length > 0 ? true : 'Au moins une valeur enum est requise'
});
field.enumValues = enumValues.split(',').map(v => v.trim());
}
// Si le champ est requis, s'assurer que la validation required est présente
if (field.required && !field.validations.includes('required')) {
field.validations.push('required');
if (!field.validationRules) {
field.validationRules = {};
}
field.validationRules.required = true;
}
// Ajouter ou mettre à jour le champ dans la liste
if (editMode) {
wizardState.fields[editIndex] = field;
console.log(chalk.green(`\nChamp "${field.name}" mis à jour avec succès!`));
editMode = false;
} else {
wizardState.fields.push(field);
console.log(chalk.green(`\nChamp "${field.name}" ajouté avec succès!`));
}
// Demander si l'utilisateur veut ajouter un autre champ
if (!editMode) {
const { action } = await inquirer.prompt({
type: 'list',
name: 'action',
message: 'Que souhaitez-vous faire maintenant?',
choices: [
{ name: 'Ajouter un autre champ', value: 'add' },
{ name: 'Continuer à l\'étape suivante', value: 'continue' },
{ name: 'Annuler et revenir à l\'étape précédente', value: 'back' }
]
});
if (action === 'continue') {
wizardState.currentStep = 'relationships';
addMoreFields = false;
} else if (action === 'back') {
if (restorePreviousState()) {
return;
} else {
wizardState.currentStep = 'modelInfo';
await collectModelInfo();
return;
}
} else {
// Continuer à ajouter des champs
addMoreFields = true;
}
}
}
}
/**
* Collecte les informations sur les relations du modèle
*/
async function collectRelationships() {
console.log(chalk.green('\n🔗 Étape 3: Configuration des relations\n'));
// Enregistrer l'état actuel avant de modifier les relations
saveStateToHistory();
// Demander si le modèle a des relations
const { hasRelationships } = await inquirer.prompt({
type: 'confirm',
name: 'hasRelationships',
message: 'Ce modèle a-t-il des relations avec d\'autres modèles?',
default: true
});
if (!hasRelationships) {
wizardState.currentStep = 'uiOptions';
return;
}
let addMoreRelationships = true;
let editMode = false;
let editIndex = -1;
while (addMoreRelationships) {
// Afficher les relations existantes
if (wizardState.relationships.length > 0) {
console.log(chalk.yellow('\nRelations déjà configurées:'));
wizardState.relationships.forEach((rel, index) => {
console.log(`${index + 1}. ${chalk.cyan(rel.name)} (${chalk.green(rel.type)}) -> ${chalk.blue(rel.relatedModel)}`);
});
console.log(); // Ligne vide pour espacement
}
// Demander à l'utilisateur ce qu'il veut faire
const { action } = await inquirer.prompt({
type: 'list',
name: 'action',
message: 'Que souhaitez-vous faire?',
choices: [
...(wizardState.relationships.length > 0 ? [{ name: 'Continuer à l\'étape suivante', value: 'continue' }] : []),
{ name: 'Ajouter une nouvelle relation', value: 'add' },
...(wizardState.relationships.length > 0 ? [
{ name: 'Modifier une relation existante', value: 'edit' },
{ name: 'Supprimer une relation', value: 'delete' }
] : []),
{ name: 'Annuler et revenir à l\'étape précédente', value: 'back' }
]
});
// Gérer l'action de l'utilisateur
if (action === 'continue') {
// Passer à l'étape suivante
wizardState.currentStep = 'uiOptions';
addMoreRelationships = false;
continue;
} else if (action === 'back') {
// Revenir à l'étape précédente
if (restorePreviousState()) {
return;
} else {
wizardState.currentStep = 'fields';
await collectFields();
return;
}
} else if (action === 'edit') {
// Sélectionner une relation à modifier
const { relationIndex } = await inquirer.prompt({
type: 'list',
name: 'relationIndex',
message: 'Quelle relation souhaitez-vous modifier?',
choices: wizardState.relationships.map((rel, index) => ({
name: `${index + 1}. ${rel.name} (${rel.type}) -> ${rel.relatedModel}`,
value: index
}))
});
editMode = true;
editIndex = relationIndex;
} else if (action === 'delete') {
// Sélectionner une relation à supprimer
const { relationIndex } = await inquirer.prompt({
type: 'list',
name: 'relationIndex',
message: 'Quelle relation souhaitez-vous supprimer?',
choices: wizardState.relationships.map((rel, index) => ({
name: `${index + 1}. ${rel.name} (${rel.type}) -> ${rel.relatedModel}`,
value: index
}))
});
// Confirmation de suppression
const { confirmDelete } = await inquirer.prompt({
type: 'confirm',
name: 'confirmDelete',
message: `Êtes-vous sûr de vouloir supprimer la relation "${wizardState.relationships[relationIndex].name}"?`,
default: false
});
if (confirmDelete) {
wizardState.relationships.splice(relationIndex, 1);
console.log(chalk.green('\nRelation supprimée avec succès!'));
}
continue;
}
// Valeurs par défaut pour la relation en mode édition
const defaults = editMode ? wizardState.relationships[editIndex] : {};
// Récupérer les informations de la relation
const relationshipPrompt = [
{
type: 'list',
name: 'type',
message: 'Type de relation:',
choices: relationshipChoices,
default: defaults.type ? relationshipChoices.findIndex(c => c.value === defaults.type) : 0
},
{
type: 'input',
name: 'name',
message: 'Nom de la relation (camelCase):',
default: defaults.name || '',
validate: input => input && input.length > 0 ? true : 'Le nom de la relation est requis'
},
{
type: 'input',
name: 'relatedModel',
message: 'Modèle associé (PascalCase):',
default: defaults.relatedModel || '',
validate: input => input && input.length > 0 ? true : 'Le modèle associé est requis'
},
{
type: 'input',
name: 'displayField',
message: 'Champ à afficher du modèle associé:',
default: defaults.displayField || 'name',
validate: input => input && input.length > 0 ? true : 'Le champ à afficher est requis'
},
{
type: 'confirm',
name: 'required',
message: 'Cette relation est-elle requise?',
default: defaults.required !== undefined ? defaults.required : false
}
];
const relationship = await inquirer.prompt(relationshipPrompt);
// Questions additionnelles en fonction du type de relation
if (relationship.type === 'belongsToMany') {
const { pivotTable } = await inquirer.prompt({
type: 'input',
name: 'pivotTable',
message: 'Nom de la table pivot:',
default: defaults.pivotTable || [wizardState.modelInfo.name, relationship.relatedModel]
.sort()
.map(name => name.toLowerCase())
.join('_')
});
relationship.pivotTable = pivotTable;
const { hasPivotFields } = await inquirer.prompt({
type: 'confirm',
name: 'hasPivotFields',
message: 'La table pivot a-t-elle des champs additionnels?',
default: defaults.withPivot && defaults.withPivot.length > 0
});
if (hasPivotFields) {
relationship.withPivot = [];
let addMorePivotFields = true;
// Utiliser les champs existants comme valeurs par défaut
const existingPivotFields = defaults.withPivot || [];
let pivotFieldIndex = 0;
while (addMorePivotFields) {
const pivotDefaults = existingPivotFields[pivotFieldIndex] || {};
const { fieldName, fieldType } = await inquirer.prompt([
{
type: 'input',
name: 'fieldName',
message: 'Nom du champ pivot:',
default: pivotDefaults.name || '',
validate: input => input && input.length > 0 ? true : 'Le nom du champ est requis'
},
{
type: 'list',
name: 'fieldType',
message: 'Type du champ:',
choices: dataTypeChoices,
default: pivotDefaults.type ? dataTypeChoices.findIndex(c => c.value === pivotDefaults.type) : 0
}
]);
relationship.withPivot.push({
name: fieldName,
type: fieldType
});
pivotFieldIndex++;
const { action } = await inquirer.prompt({
type: 'list',
name: 'action',
message: 'Que souhaitez-vous faire maintenant?',
choices: [
{ name: 'Ajouter un autre champ pivot', value: 'add' },
{ name: 'Terminer la configuration des champs pivot', value: 'finish' }
]
});
addMorePivotFields = action === 'add';
}
}
} else if (['hasMany', 'hasOne'].includes(relationship.type)) {
// Questions spécifiques pour hasMany et hasOne
const { foreignKey } = await inquirer.prompt({
type: 'input',
name: 'foreignKey',
message: 'Clé étrangère dans le modèle associé:',
default: defaults.foreignKey || `${wizardState.modelInfo.name.toLowerCase()}_id`
});
relationship.foreignKey = foreignKey;
} else if (relationship.type === 'belongsTo') {
// Questions spécifiques pour belongsTo
const { foreignKey } = await inquirer.prompt({
type: 'input',
name: 'foreignKey',
message: 'Clé étrangère dans ce modèle:',
default: defaults.foreignKey || `${relationship.name}_id`
});
relationship.foreignKey = foreignKey;
}
// Configuration des options d'affichage dans l'UI
const { includeInUi } = await inquirer.prompt({
type: 'confirm',
name: 'includeInUi',
message: 'Afficher cette relation dans l\'interface utilisateur?',
default: defaults.ui?.showInForm !== undefined ? defaults.ui.showInForm : true
});
if (includeInUi) {
const uiOptions = await inquirer.prompt([
{
type: 'confirm',
name: 'showInTable',
message: 'Afficher dans le tableau de données?',
default: defaults.ui?.showInTable !== undefined ? defaults.ui.showInTable : (relationship.type === 'belongsTo')
},
{
type: 'confirm',
name: 'showInForm',
message: 'Afficher dans le formulaire?',
default: defaults.ui?.showInForm !== undefined ? defaults.ui.showInForm : true
},
{
type: 'input',
name: 'label',
message: 'Label pour l\'UI:',
default: defaults.ui?.label || relationship.name.charAt(0).toUpperCase() +
relationship.name.slice(1).replace(/([A-Z])/g, ' $1')
}
]);
relationship.ui = uiOptions;
}
// Ajouter ou mettre à jour la relation dans la liste
if (editMode) {
wizardState.relationships[editIndex] = relationship;
console.log(chalk.green(`\nRelation "${relationship.name}" mise à jour avec succès!`));
editMode = false;
} else {
wizardState.relationships.push(relationship);
console.log(chalk.green(`\nRelation "${relationship.name}" ajoutée avec succès!`));
}
// Demander si l'utilisateur veut ajouter une autre relation
if (!editMode) {
const { action } = await inquirer.prompt({
type: 'list',
name: 'action',
message: 'Que souhaitez-vous faire maintenant?',
choices: [
{ name: 'Ajouter une autre relation', value: 'add' },
{ name: 'Continuer à l\'étape suivante', value: 'continue' },
{ name: 'Annuler et revenir à l\'étape précédente', value: 'back' }
]
});
if (action === 'continue') {
wizardState.currentStep = 'uiOptions';
addMoreRelationships = false;
} else if (action === 'back') {
if (restorePreviousState()) {
return;
} else {
wizardState.currentStep = 'fields';
await collectFields();
return;
}
} else {
// Continuer à ajouter des relations
addMoreRelationships = true;
}
}
}
}
/**
* Collecte les options d'UI pour le modèle
*/
async function collectUIOptions() {
console.log(chalk.green('\n🎨 Étape 4: Configuration des options d\'UI\n'));
// Enregistrer l'état actuel avant de modifier les options d'UI
saveStateToHistory();
// Préparer les choix pour les champs à afficher
const fieldChoices = wizardState.fields.map(f => ({ name: f.label || f.name, value: f.name }));
// Ajouter les champs de relation pour l'affichage dans le tableau
const relationFieldChoices = [];
wizardState.relationships.forEach(rel => {
if (rel.type === 'belongsTo') {
relationFieldChoices.push({
name: `${rel.name}.${rel.displayField} (Relation)`,
value: `${rel.name}.${rel.displayField}`
});
}
});
// Combiner les choix
const allFieldChoices = [...fieldChoices, ...relationFieldChoices];
// Valeurs par défaut pour les options d'UI
const defaultTableFields = wizardState.fields.slice(0, 4).map(f => f.name);
// Récupérer les options d'UI
const uiOptions = await inquirer.prompt([
{
type: 'checkbox',
name: 'tableFields',
message: 'Champs à afficher dans le tableau de données:',
choices: allFieldChoices,
default: wizardState.ui?.tableFields || defaultTableFields
},
{
type: 'input',
name: 'itemsPerPage',
message: 'Nombre d\'éléments par page par défaut:',
default: wizardState.ui?.itemsPerPage || 10,
validate: input => !isNaN(input) ? true : 'Veuillez entrer un nombre',
filter: input => parseInt(input)
},
{
type: 'confirm',
name: 'enableSearch',
message: 'Activer la recherche globale?',
default: wizardState.ui?.enableSearch !== undefined ? wizardState.ui.enableSearch : true
},
{
type: 'confirm',
name: 'enableFilters',
message: 'Activer les filtres de colonnes?',
default: wizardState.ui?.enableFilters !== undefined ? wizardState.ui.enableFilters : true
},
{
type: 'list',
name: 'action',
message: 'Que souhaitez-vous faire maintenant?',
choices: [
{ name: 'Continuer à l\'étape suivante', value: 'continue' },
{ name: 'Annuler et revenir à l\'étape précédente', value: 'back' }
]
}
]);
// Gérer l'action de l'utilisateur
if (uiOptions.action === 'back') {
if (restorePreviousState()) {
return;
} else {
wizardState.currentStep = 'relationships';
await collectRelationships();
return;
}
}
// Supprimer la propriété 'action' avant de sauvegarder
delete uiOptions.action;
// Mettre à jour l'état avec les nouvelles options d'UI
wizardState.ui = uiOptions;
wizardState.currentStep = 'saveConfig';
}
/**
* Enregistre la configuration finale
*/
async function saveConfiguration() {
console.log(chalk.green('\n💾 Étape 5: Enregistrement de la configuration\n'));
// Enregistrer l'état actuel avant de sauvegarder
saveStateToHistory();
// Demander le nom du fichier
const { fileName, action } = await inquirer.prompt([
{
type: 'input',
name: 'fileName',
message: 'Enregistrer la configuration sous:',
default: `${wizardState.modelInfo.name.toLowerCase()}.yml`
},
{
type: 'list',
name: 'action',
message: 'Que souhaitez-vous faire maintenant?',
choices: [
{ name: 'Enregistrer et terminer', value: 'save' },
{ name: 'Annuler et revenir à l\'étape précédente', value: 'back' }
]
}
]);
// Gérer l'action de l'utilisateur
if (action === 'back') {
if (restorePreviousState()) {
return await saveConfiguration();
} else {
wizardState.currentStep = 'uiOptions';
await collectUIOptions();
return;
}
}
try {
// Construire l'objet de configuration finale
const config = await buildConfigObject(
{
name: wizardState.modelInfo.name,
tableName: wizardState.modelInfo.tableName,
displayName: wizardState.modelInfo.displayName,
description: wizardState.modelInfo.description
},
wizardState.fields,
wizardState.relationships,
wizardState.ui,
{ // Options de routage par défaut
routePrefix: wizardState.modelInfo.tableName,
frontendRoute: wizardState.modelInfo.tableName.replace(/_/g, '-'),
menuTitle: wizardState.modelInfo.displayName,
menuIcon: 'list'
}
);
// Enregistrer la configuration
const filePath = await saveConfigFile(config, fileName, 'examples');
console.log(chalk.green(`\nConfiguration enregistrée avec succès dans ${filePath}\n`));
// Demander si l'utilisateur veut générer les composants CRUD maintenant
const { generateNow } = await inquirer.prompt({
type: 'confirm',
name: 'generateNow',
message: 'Générer les composants CRUD maintenant?',
default: true
});
if (generateNow) {
try {
console.log(chalk.blue('Lancement de la génération CRUD...'));
// Utiliser le script dédié à la génération CRUD
const generateCrudPath = path.resolve(__dirname, '../generate-crud.js');
// Exécuter le script directement en l'important
try {
const { runGenerator } = require(generateCrudPath);
runGenerator(filePath);
} catch (importError) {
console.error(chalk.red(`Erreur lors de l'importation du générateur: ${importError.message}`));
// Fallback: exécuter en tant que processus séparé
const { exec } = require('child_process');
exec(`node "${generateCrudPath}" "${filePath}"`, (error, stdout, stderr) => {
// Afficher la sortie
if (stdout) console.log(stdout);
if (stderr) console.error(chalk.red(stderr));
if (error) {
console.log(chalk.yellow('\nErreur lors de la génération. Veuillez utiliser le menu principal.\n'));
}
});
}
console.log(chalk.green('La génération a été lancée. Utilisez le menu principal pour plus d\'options.'));
} catch (error) {
console.log(chalk.yellow(`\nErreur lors de la préparation de la génération: ${error.message}\nVeuillez utiliser le menu principal pour générer le CRUD.\n`));
}
}
return config;
} catch (error) {
console.error(chalk.red(`Erreur: ${error.message}`));
return null;
}
}
module.exports = {
runModelWizard
};