UNPKG

kira-crud

Version:

Intelligent CRUD Generator for Laravel and Angular

420 lines (371 loc) 12.1 kB
/** * Project Configuration Manager * Manage paths and settings for Laravel and Angular projects */ const fs = require('fs').promises; const path = require('path'); const os = require('os'); const chalk = require('chalk'); const inquirer = require('inquirer'); const { exec } = require('child_process'); const util = require('util'); const execPromise = util.promisify(exec); const settingsManager = require('./settings-manager'); // Configuration file path const CONFIG_FILE_PATH = path.join(os.homedir(), '.kira-config.json'); /** * Default configuration */ const DEFAULT_CONFIG = { version: '1.0.0', projects: {}, defaultProject: null, templates: { // Utiliser des chemins absolus pour les templates (compatibles avec npm global) laravel: path.join(__dirname, '../templates/laravel'), angular: path.join(__dirname, '../templates/angular-custom'), polymorphic: path.join(__dirname, '../templates/polymorphic') } }; /** * Load the configuration file * @returns {Promise<Object>} Configuration object */ async function loadConfig() { try { const exists = await fileExists(CONFIG_FILE_PATH); if (exists) { const configContent = await fs.readFile(CONFIG_FILE_PATH, 'utf8'); return JSON.parse(configContent); } else { // Create default config if it doesn't exist await saveConfig(DEFAULT_CONFIG); return DEFAULT_CONFIG; } } catch (error) { console.error(chalk.red(`Error loading configuration: ${error.message}`)); return DEFAULT_CONFIG; } } /** * Save the configuration file * @param {Object} config - Configuration object * @returns {Promise<void>} */ async function saveConfig(config) { try { await fs.writeFile(CONFIG_FILE_PATH, JSON.stringify(config, null, 2), 'utf8'); } catch (error) { console.error(chalk.red(`Error saving configuration: ${error.message}`)); throw error; } } /** * Check if a file exists * @param {string} filePath - Path to check * @returns {Promise<boolean>} Whether the file exists */ async function fileExists(filePath) { try { await fs.access(filePath); return true; } catch (error) { return false; } } /** * Check if a directory contains a Laravel project * @param {string} directory - Directory to check * @returns {Promise<boolean>} Whether the directory contains a Laravel project */ async function isLaravelProject(directory) { try { const artisanPath = path.join(directory, 'artisan'); const composerPath = path.join(directory, 'composer.json'); const artisanExists = await fileExists(artisanPath); const composerExists = await fileExists(composerPath); if (artisanExists && composerExists) { // Check if it's a Laravel project by looking at composer.json const composerContent = await fs.readFile(composerPath, 'utf8'); const composer = JSON.parse(composerContent); return composer.require && (composer.require['laravel/framework'] || composer.name === 'laravel/laravel'); } return false; } catch (error) { return false; } } /** * Check if a directory contains an Angular project * @param {string} directory - Directory to check * @returns {Promise<boolean>} Whether the directory contains an Angular project */ async function isAngularProject(directory) { try { const packagePath = path.join(directory, 'package.json'); if (await fileExists(packagePath)) { // Check if it's an Angular project by looking at package.json const packageContent = await fs.readFile(packagePath, 'utf8'); const packageJson = JSON.parse(packageContent); return packageJson.dependencies && (packageJson.dependencies['@angular/core'] || packageJson.devDependencies && packageJson.devDependencies['@angular/cli']); } return false; } catch (error) { return false; } } /** * Detect Laravel and Angular projects in the current directory and subdirectories * @param {string} baseDirectory - Base directory to start searching * @param {number} depth - Maximum search depth * @returns {Promise<Object>} Detected projects */ async function detectProjects(baseDirectory, depth = 2) { const projects = { laravel: [], angular: [] }; async function scanDirectory(directory, currentDepth) { if (currentDepth > depth) return; try { // Check if the current directory is a Laravel or Angular project if (await isLaravelProject(directory)) { projects.laravel.push(directory); } if (await isAngularProject(directory)) { projects.angular.push(directory); } // Scan subdirectories if (currentDepth < depth) { const entries = await fs.readdir(directory, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules' && entry.name !== 'vendor') { await scanDirectory(path.join(directory, entry.name), currentDepth + 1); } } } } catch (error) { // Skip directories we can't read } } await scanDirectory(baseDirectory, 0); return projects; } /** * Configure a project interactively * @param {Object} config - Current configuration * @returns {Promise<Object>} Updated configuration */ async function configureProject(config) { console.log(chalk.blue('\nProject Configuration')); console.log(chalk.blue('=====================')); // Load user settings const settings = await settingsManager.loadSettings(); const defaultBackendPath = settings.paths?.backend || './back'; const defaultFrontendPath = settings.paths?.frontend || './front'; // Ask for project name const { projectName } = await inquirer.prompt([ { type: 'input', name: 'projectName', message: 'Enter a name for this project:', default: path.basename(process.cwd()), validate: (input) => input.trim() !== '' ? true : 'Project name is required' } ]); // Ask for Laravel path const { laravelPath } = await inquirer.prompt([ { type: 'input', name: 'laravelPath', message: 'Enter the path to your Laravel project:', default: defaultBackendPath, validate: async (input) => { const fullPath = path.resolve(input); if (await isLaravelProject(fullPath)) { return true; } return 'Path does not contain a valid Laravel project'; } } ]); // Ask for Angular path const { angularPath } = await inquirer.prompt([ { type: 'input', name: 'angularPath', message: 'Enter the path to your Angular project:', default: defaultFrontendPath, validate: async (input) => { const fullPath = path.resolve(input); if (await isAngularProject(fullPath)) { return true; } return 'Path does not contain a valid Angular project'; } } ]); // Ask for default settings path const { settingsPath } = await inquirer.prompt([ { type: 'input', name: 'settingsPath', message: 'Enter the default path for settings components (relative to Angular src):', default: 'app/pages/admin/settings' } ]); // Update configuration config.projects[projectName] = { laravel: { path: path.resolve(laravelPath) }, angular: { path: path.resolve(angularPath), settingsPath }, createdAt: new Date().toISOString() }; // Set as default if first project if (!config.defaultProject) { config.defaultProject = projectName; } else { // Ask if it should be the default const { makeDefault } = await inquirer.prompt([ { type: 'confirm', name: 'makeDefault', message: 'Set this as the default project?', default: false } ]); if (makeDefault) { config.defaultProject = projectName; } } // Save configuration await saveConfig(config); console.log(chalk.green(`\nProject '${projectName}' configured successfully!`)); return config; } /** * Get the active project configuration * @param {string} projectName - Optional project name, uses default if not provided * @returns {Promise<Object>} Project configuration */ async function getActiveProject(projectName) { const config = await loadConfig(); if (!projectName) { projectName = config.defaultProject; } if (!projectName || !config.projects[projectName]) { throw new Error('No active project configured. Run "kira config" to set up a project.'); } return { name: projectName, ...config.projects[projectName] }; } /** * List all configured projects * @returns {Promise<Array>} List of projects */ async function listProjects() { const config = await loadConfig(); return Object.entries(config.projects).map(([name, project]) => ({ name, laravel: project.laravel.path, angular: project.angular.path, isDefault: name === config.defaultProject })); } /** * Get template directory paths * @returns {Promise<Object>} Template paths */ async function getTemplatePaths() { const config = await loadConfig(); return config.templates; } /** * Update template paths * @param {Object} templates - New template paths * @returns {Promise<void>} */ async function updateTemplatePaths(templates) { const config = await loadConfig(); config.templates = { ...config.templates, ...templates }; await saveConfig(config); } /** * Get the absolute path for a project component * @param {Object} project - Project configuration * @param {string} component - Component type ('laravel' or 'angular') * @param {string} relativePath - Relative path within the component * @returns {string} Absolute path */ function getProjectPath(project, component, relativePath = '') { if (!project || !project[component] || !project[component].path) { // Synchronous version with defaults for backward compatibility if (component === 'laravel') { return path.join(path.resolve('./back'), relativePath); } else if (component === 'angular') { return path.join(path.resolve('./front'), relativePath); } else { throw new Error(`Invalid project configuration for ${component}`); } } return path.join(project[component].path, relativePath); } /** * Get the absolute path for a project component (async version with settings) * @param {Object} project - Project configuration * @param {string} component - Component type ('laravel' or 'angular') * @param {string} relativePath - Relative path within the component * @returns {Promise<string>} Absolute path */ async function getProjectPathAsync(project, component, relativePath = '') { if (!project || !project[component] || !project[component].path) { // Fallback to user settings if project configuration is invalid const settings = await settingsManager.loadSettings(); if (component === 'laravel') { const defaultPath = settings.paths?.backend || './back'; return path.join(path.resolve(defaultPath), relativePath); } else if (component === 'angular') { const defaultPath = settings.paths?.frontend || './front'; return path.join(path.resolve(defaultPath), relativePath); } else { throw new Error(`Invalid project configuration for ${component}`); } } return path.join(project[component].path, relativePath); } /** * Get project paths from settings * @returns {Promise<Object>} Default paths from settings */ async function getDefaultPaths() { const settings = await settingsManager.loadSettings(); return { backend: settings.paths?.backend || './back', frontend: settings.paths?.frontend || './front' }; } module.exports = { loadConfig, saveConfig, detectProjects, configureProject, getActiveProject, listProjects, getTemplatePaths, updateTemplatePaths, getProjectPath, getProjectPathAsync, isLaravelProject, isAngularProject, getDefaultPaths };