kira-crud
Version:
Intelligent CRUD Generator for Laravel and Angular
420 lines (371 loc) • 12.1 kB
JavaScript
/**
* 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
};