kira-crud
Version:
Intelligent CRUD Generator for Laravel and Angular
864 lines (750 loc) • 26.8 kB
JavaScript
/**
* Purge System Module for Kira CLI
*
* This module provides functions for cleaning up generated components,
* adapted from the standalone purge-system.js for integration with the Kira CLI.
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const chalk = require('chalk');
const { isFeminine, getDefiniteArticle, getIndefiniteArticle, toPlural } = require('./french-language-utils');
// File paths are relative to the project root
const PROJECT_ROOT = path.resolve(process.cwd());
/**
* Get all generated components in the project
* @returns {Promise<Array>} Array of component objects
*/
async function getGeneratedComponents() {
const components = [];
// Detect Angular components
const angularComponentsPath = path.join(PROJECT_ROOT, 'front/src/app/pages/admin/settings');
if (fs.existsSync(angularComponentsPath)) {
const dirs = fs.readdirSync(angularComponentsPath).filter(dir => {
const dirPath = path.join(angularComponentsPath, dir);
return fs.statSync(dirPath).isDirectory();
});
for (const dir of dirs) {
const component = {
name: dir,
type: 'Fullstack',
frontend: true,
backend: false,
files: [],
angularPath: path.join(angularComponentsPath, dir)
};
// Check if component files exist
const componentFiles = fs.readdirSync(component.angularPath);
component.files = componentFiles.map(file => path.join(component.angularPath, file));
// Check if the backend exists
const modelPath = path.join(PROJECT_ROOT, 'back/app/Models', `${pascalCase(dir)}.php`);
if (fs.existsSync(modelPath)) {
component.backend = true;
component.modelName = pascalCase(dir);
// Add backend files to the list
component.files.push(modelPath);
const controllerPath = path.join(PROJECT_ROOT, 'back/app/Http/Controllers/Api', `${pascalCase(dir)}Controller.php`);
if (fs.existsSync(controllerPath)) {
component.files.push(controllerPath);
}
}
components.push(component);
}
}
return components;
}
/**
* Backup important system files before purging
* @returns {Promise<void>}
*/
async function backupSystemFiles() {
console.log(chalk.blue('Backing up system files...'));
const systemFiles = [
'front/src/app/app.routes.ts',
'front/src/app/paths.ts',
'front/src/app/shared/components/sidebar/sidebar.component.ts',
'back/routes/api.php',
'back/app/Providers/CrudBindingServiceProvider.php'
];
const backupDir = path.join(PROJECT_ROOT, '.kira-backups', 'system-backup');
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir, { recursive: true });
}
for (const file of systemFiles) {
const sourcePath = path.join(PROJECT_ROOT, file);
if (fs.existsSync(sourcePath)) {
const backupPath = path.join(backupDir, file.replace(/\//g, '_'));
fs.copyFileSync(sourcePath, backupPath);
}
}
}
/**
* Restore system files after purge
* @returns {Promise<void>}
*/
async function restoreSystemFiles() {
console.log(chalk.blue('Restoring system files...'));
const backupDir = path.join(PROJECT_ROOT, '.kira-backups', 'system-backup');
if (!fs.existsSync(backupDir)) return;
const systemFiles = [
'front_src_app_app.routes.ts',
'front_src_app_paths.ts',
'front_src_app_shared_components_sidebar_sidebar.component.ts',
'back_routes_api.php',
'back_app_Providers_CrudBindingServiceProvider.php'
];
for (const file of systemFiles) {
const backupPath = path.join(backupDir, file);
if (fs.existsSync(backupPath)) {
const originalPath = path.join(PROJECT_ROOT, file.replace(/_/g, '/'));
// Restore file based on type
if (file.includes('app.routes.ts')) {
await restoreCleanAppRoutes(originalPath);
} else if (file.includes('paths.ts')) {
await restoreCleanPaths(originalPath);
} else if (file.includes('sidebar.component.ts')) {
await restoreCleanSidebar(originalPath);
} else if (file.includes('api.php')) {
await restoreCleanApiRoutes(originalPath);
} else if (file.includes('CrudBindingServiceProvider.php')) {
await restoreCleanServiceProvider(originalPath);
}
}
}
}
/**
* Purge Angular components with transaction support
* @param {Object} transaction - Transaction manager instance
* @returns {Promise<void>}
*/
async function purgeAngularComponents(transaction) {
console.log(chalk.blue('Removing Angular components...'));
const componentsPath = path.join(PROJECT_ROOT, 'front/src/app/pages/admin/settings');
if (fs.existsSync(componentsPath)) {
const dirs = fs.readdirSync(componentsPath).filter(dir => {
const dirPath = path.join(componentsPath, dir);
return fs.statSync(dirPath).isDirectory();
});
for (const dir of dirs) {
const dirPath = path.join(componentsPath, dir);
// Add delete operations to the transaction if provided
if (transaction) {
const files = fs.readdirSync(dirPath).map(file => path.join(dirPath, file));
for (const file of files) {
await transaction.addDelete(file);
}
} else {
fs.rmSync(dirPath, { recursive: true, force: true });
}
console.log(chalk.red(` ❌ ${dir}`));
}
}
// Remove YAML configuration files
const frontDir = path.join(PROJECT_ROOT, 'front');
if (fs.existsSync(frontDir)) {
const yamlFiles = fs.readdirSync(frontDir).filter(file => file.endsWith('.crud.yaml') || file.endsWith('.crud.yml'));
for (const file of yamlFiles) {
const filePath = path.join(frontDir, file);
// Add to transaction if provided
if (transaction) {
await transaction.addDelete(filePath);
} else {
fs.unlinkSync(filePath);
}
console.log(chalk.red(` ❌ ${file}`));
}
}
}
/**
* Purge Laravel components with transaction support
* @param {Object} transaction - Transaction manager instance
* @returns {Promise<void>}
*/
async function purgeLaravelComponents(transaction) {
console.log(chalk.blue('Removing Laravel components...'));
const backPath = path.join(PROJECT_ROOT, 'back');
// Remove models (keep User.php)
const modelsPath = path.join(backPath, 'app/Models');
if (fs.existsSync(modelsPath)) {
const models = fs.readdirSync(modelsPath).filter(file =>
file.endsWith('.php') && file !== 'User.php'
);
for (const model of models) {
const modelPath = path.join(modelsPath, model);
// Add to transaction if provided
if (transaction) {
await transaction.addDelete(modelPath);
} else {
fs.unlinkSync(modelPath);
}
console.log(chalk.red(` ❌ Model: ${model}`));
}
}
// Remove repositories
const repoPath = path.join(backPath, 'app/Repositories');
if (fs.existsSync(repoPath)) {
// Add directories to transaction if provided
if (transaction) {
const repoDirs = ['', 'Interfaces'];
for (const dir of repoDirs) {
const repoSubPath = dir ? path.join(repoPath, dir) : repoPath;
if (fs.existsSync(repoSubPath)) {
const files = fs.readdirSync(repoSubPath).filter(file => file.endsWith('.php'));
for (const file of files) {
await transaction.addDelete(path.join(repoSubPath, file));
}
}
}
} else {
fs.rmSync(repoPath, { recursive: true, force: true });
}
console.log(chalk.red(' ❌ Repositories'));
}
// Remove services
const servicesPath = path.join(backPath, 'app/Services');
if (fs.existsSync(servicesPath)) {
// Add directories to transaction if provided
if (transaction) {
const serviceDirs = ['', 'Interfaces'];
for (const dir of serviceDirs) {
const serviceSubPath = dir ? path.join(servicesPath, dir) : servicesPath;
if (fs.existsSync(serviceSubPath)) {
const files = fs.readdirSync(serviceSubPath).filter(file => file.endsWith('.php'));
for (const file of files) {
await transaction.addDelete(path.join(serviceSubPath, file));
}
}
}
} else {
fs.rmSync(servicesPath, { recursive: true, force: true });
}
console.log(chalk.red(' ❌ Services'));
}
// Remove API controllers
const controllersPath = path.join(backPath, 'app/Http/Controllers/Api');
if (fs.existsSync(controllersPath)) {
// Add directories to transaction if provided
if (transaction) {
const files = fs.readdirSync(controllersPath).filter(file => file.endsWith('.php'));
for (const file of files) {
await transaction.addDelete(path.join(controllersPath, file));
}
} else {
fs.rmSync(controllersPath, { recursive: true, force: true });
}
console.log(chalk.red(' ❌ API Controllers'));
}
// Remove resources (keep UserResource)
const resourcesPath = path.join(backPath, 'app/Http/Resources');
if (fs.existsSync(resourcesPath)) {
const resources = fs.readdirSync(resourcesPath).filter(file =>
file.endsWith('.php') && !file.includes('UserResource')
);
for (const resource of resources) {
const resourcePath = path.join(resourcesPath, resource);
// Add to transaction if provided
if (transaction) {
await transaction.addDelete(resourcePath);
} else {
fs.unlinkSync(resourcePath);
}
console.log(chalk.red(` ❌ Resource: ${resource}`));
}
}
// Remove requests (keep Auth)
const requestsPath = path.join(backPath, 'app/Http/Requests');
if (fs.existsSync(requestsPath)) {
const dirs = fs.readdirSync(requestsPath).filter(dir => {
const dirPath = path.join(requestsPath, dir);
return fs.statSync(dirPath).isDirectory() && dir !== 'Auth';
});
for (const dir of dirs) {
const dirPath = path.join(requestsPath, dir);
// Add to transaction if provided
if (transaction) {
const files = fs.readdirSync(dirPath).map(file => path.join(dirPath, file));
for (const file of files) {
await transaction.addDelete(file);
}
} else {
fs.rmSync(dirPath, { recursive: true, force: true });
}
console.log(chalk.red(` ❌ Requests: ${dir}`));
}
}
// Remove model parameters
const parametersPath = path.join(backPath, 'app/ModelParameters');
if (fs.existsSync(parametersPath)) {
// Add to transaction if provided
if (transaction) {
const files = fs.readdirSync(parametersPath).filter(file => file.endsWith('.php'));
for (const file of files) {
await transaction.addDelete(path.join(parametersPath, file));
}
} else {
fs.rmSync(parametersPath, { recursive: true, force: true });
}
console.log(chalk.red(' ❌ Model Parameters'));
}
// Remove migrations (keep base migrations)
const migrationsPath = path.join(backPath, 'database/migrations');
if (fs.existsSync(migrationsPath)) {
const migrations = fs.readdirSync(migrationsPath).filter(file =>
file.endsWith('.php') &&
!file.includes('create_users_table') &&
!file.includes('create_password_resets_table') &&
!file.includes('create_failed_jobs_table') &&
!file.includes('create_personal_access_tokens_table')
);
for (const migration of migrations) {
const migrationPath = path.join(migrationsPath, migration);
// Add to transaction if provided
if (transaction) {
await transaction.addDelete(migrationPath);
} else {
fs.unlinkSync(migrationPath);
}
console.log(chalk.red(` ❌ Migration: ${migration}`));
}
}
}
/**
* Purge a specific component with transaction support
* @param {Object} component - The component to purge
* @param {Object} transaction - Transaction manager instance
* @returns {Promise<void>}
*/
async function purgeSpecificComponent(component, transaction) {
const article = getDefiniteArticle(component.name);
const isFem = isFeminine(component.name);
console.log(chalk.blue(`Suppression ${isFem ? 'de la' : 'du'} composant ${component.name}...`));
// Remove Angular component
if (component.frontend && fs.existsSync(component.angularPath)) {
// Add files to transaction if provided
if (transaction) {
const files = fs.readdirSync(component.angularPath).map(file =>
path.join(component.angularPath, file)
);
for (const file of files) {
await transaction.addDelete(file);
}
} else {
fs.rmSync(component.angularPath, { recursive: true, force: true });
}
console.log(chalk.red(` ❌ Frontend: ${component.name}`));
}
// Remove Laravel files
if (component.backend && component.modelName) {
await purgeLaravelComponentByName(component.modelName, transaction);
}
// Clean routes and bindings
await cleanSpecificComponentRoutes(component.name);
if (component.modelName) {
await cleanSpecificComponentBindings(component.modelName);
}
}
/**
* Purge a Laravel component by name with transaction support
* @param {string} modelName - The model name to purge
* @param {Object} transaction - Transaction manager instance
* @returns {Promise<void>}
*/
async function purgeLaravelComponentByName(modelName, transaction) {
const backPath = path.join(PROJECT_ROOT, 'back');
// Model
const modelPath = path.join(backPath, `app/Models/${modelName}.php`);
if (fs.existsSync(modelPath)) {
if (transaction) {
await transaction.addDelete(modelPath);
} else {
fs.unlinkSync(modelPath);
}
}
// Repository
const repoPath = path.join(backPath, `app/Repositories/${modelName}Repository.php`);
const repoInterfacePath = path.join(backPath, `app/Repositories/Interfaces/${modelName}RepositoryInterface.php`);
if (fs.existsSync(repoPath)) {
if (transaction) {
await transaction.addDelete(repoPath);
} else {
fs.unlinkSync(repoPath);
}
}
if (fs.existsSync(repoInterfacePath)) {
if (transaction) {
await transaction.addDelete(repoInterfacePath);
} else {
fs.unlinkSync(repoInterfacePath);
}
}
// Service
const servicePath = path.join(backPath, `app/Services/${modelName}Service.php`);
const serviceInterfacePath = path.join(backPath, `app/Services/Interfaces/${modelName}ServiceInterface.php`);
if (fs.existsSync(servicePath)) {
if (transaction) {
await transaction.addDelete(servicePath);
} else {
fs.unlinkSync(servicePath);
}
}
if (fs.existsSync(serviceInterfacePath)) {
if (transaction) {
await transaction.addDelete(serviceInterfacePath);
} else {
fs.unlinkSync(serviceInterfacePath);
}
}
// Controller
const controllerPath = path.join(backPath, `app/Http/Controllers/Api/${modelName}Controller.php`);
if (fs.existsSync(controllerPath)) {
if (transaction) {
await transaction.addDelete(controllerPath);
} else {
fs.unlinkSync(controllerPath);
}
}
// Resources
const resourcePath = path.join(backPath, `app/Http/Resources/${modelName}Resource.php`);
const collectionPath = path.join(backPath, `app/Http/Resources/${modelName}Collection.php`);
if (fs.existsSync(resourcePath)) {
if (transaction) {
await transaction.addDelete(resourcePath);
} else {
fs.unlinkSync(resourcePath);
}
}
if (fs.existsSync(collectionPath)) {
if (transaction) {
await transaction.addDelete(collectionPath);
} else {
fs.unlinkSync(collectionPath);
}
}
// Requests
const requestDir = path.join(backPath, `app/Http/Requests/${modelName}`);
if (fs.existsSync(requestDir)) {
if (transaction) {
// Add all files in the directory to the transaction
const requestFiles = fs.readdirSync(requestDir).map(file => path.join(requestDir, file));
for (const file of requestFiles) {
await transaction.addDelete(file);
}
} else {
fs.rmSync(requestDir, { recursive: true, force: true });
}
}
// Migration
const migrationsPath = path.join(backPath, 'database/migrations');
if (fs.existsSync(migrationsPath)) {
const tableName = modelName.toLowerCase() + 's';
const migrations = fs.readdirSync(migrationsPath).filter(file =>
file.includes(`create_${tableName}_table`)
);
for (const migration of migrations) {
const migrationPath = path.join(migrationsPath, migration);
if (transaction) {
await transaction.addDelete(migrationPath);
} else {
fs.unlinkSync(migrationPath);
}
}
}
// Model Parameters
const paramPath = path.join(backPath, `app/ModelParameters/${modelName}Parameters.php`);
if (fs.existsSync(paramPath)) {
if (transaction) {
await transaction.addDelete(paramPath);
} else {
fs.unlinkSync(paramPath);
}
}
console.log(chalk.red(` ❌ Backend: ${modelName}`));
}
/**
* Clean Angular routes
* @returns {Promise<void>}
*/
async function cleanAngularRoutes() {
console.log(chalk.blue('Cleaning Angular routes...'));
await restoreCleanAppRoutes(path.join(PROJECT_ROOT, 'front/src/app/app.routes.ts'));
await restoreCleanPaths(path.join(PROJECT_ROOT, 'front/src/app/paths.ts'));
}
/**
* Clean Angular sidebar
* @returns {Promise<void>}
*/
async function cleanAngularSidebar() {
console.log(chalk.blue('Cleaning Angular sidebar...'));
await restoreCleanSidebar(path.join(PROJECT_ROOT, 'front/src/app/shared/components/sidebar/sidebar.component.ts'));
}
/**
* Clean Laravel routes
* @returns {Promise<void>}
*/
async function cleanLaravelRoutes() {
console.log(chalk.blue('Cleaning Laravel routes...'));
await restoreCleanApiRoutes(path.join(PROJECT_ROOT, 'back/routes/api.php'));
}
/**
* Clean service bindings
* @returns {Promise<void>}
*/
async function cleanServiceBindings() {
console.log(chalk.blue('Cleaning service bindings...'));
await restoreCleanServiceProvider(path.join(PROJECT_ROOT, 'back/app/Providers/CrudBindingServiceProvider.php'));
}
/**
* Clean routes for a specific component
* @param {string} componentName - Name of the component
* @returns {Promise<void>}
*/
async function cleanSpecificComponentRoutes(componentName) {
// Clean Angular-specific routes
const pathsFile = path.join(PROJECT_ROOT, 'front/src/app/paths.ts');
const routesFile = path.join(PROJECT_ROOT, 'front/src/app/app.routes.ts');
const sidebarFile = path.join(PROJECT_ROOT, 'front/src/app/shared/components/sidebar/sidebar.component.ts');
const constName = componentName.toUpperCase().replace(/[^A-Z0-9]/g, '_');
// Remove from paths.ts
if (fs.existsSync(pathsFile)) {
let content = fs.readFileSync(pathsFile, 'utf8');
content = content.replace(new RegExp(`\\s*${constName}\\s*=\\s*[^,]+,?`, 'g'), '');
fs.writeFileSync(pathsFile, content);
}
// Remove from app.routes.ts
if (fs.existsSync(routesFile)) {
let content = fs.readFileSync(routesFile, 'utf8');
const componentClass = pascalCase(componentName) + 'Component';
content = content.replace(new RegExp(`import.*${componentClass}.*from.*\\n`, 'g'), '');
content = content.replace(new RegExp(`\\s*\\{[^}]*path:\\s*Paths\\.${constName}[^}]*\\},?\\n`, 'g'), '');
fs.writeFileSync(routesFile, content);
}
// Remove from sidebar
if (fs.existsSync(sidebarFile)) {
let content = fs.readFileSync(sidebarFile, 'utf8');
// First, check if we need to remove an entry
if (content.includes(`this.paths.${constName}`)) {
content = content.replace(new RegExp(`\\s*\\{[^}]*this\\.paths\\.${constName}[^}]*\\},?\\n`, 'g'), '');
// If we're modifying the menuConfig, make sure we preserve the Dashboard entry
const ngOnInitRegex = /ngOnInit\s*\(\)\s*\{[\s\S]*?\}/;
const ngOnInitMatch = content.match(ngOnInitRegex);
if (ngOnInitMatch && !content.includes('label: \'Dashboard\'')) {
// Replace with our preserved version if Dashboard is missing
const preservedNgOnInit = `ngOnInit() {
this.menuConfig = [
{
label: 'Dashboard',
link: Paths.DASHBOARD,
icon: 'chart-line',
},
];
}`;
content = content.replace(ngOnInitRegex, preservedNgOnInit);
}
fs.writeFileSync(sidebarFile, content);
}
}
}
/**
* Clean bindings for a specific component
* @param {string} modelName - Name of the model
* @returns {Promise<void>}
*/
async function cleanSpecificComponentBindings(modelName) {
if (!modelName) return;
const providerPath = path.join(PROJECT_ROOT, 'back/app/Providers/CrudBindingServiceProvider.php');
if (fs.existsSync(providerPath)) {
let content = fs.readFileSync(providerPath, 'utf8');
content = content.replace(new RegExp(`\\s*\\$this->app->bind\\(.*${modelName}.*\\);\\n`, 'g'), '');
fs.writeFileSync(providerPath, content);
}
}
/**
* Drop generated database tables
* @returns {Promise<void>}
*/
async function dropGeneratedTables() {
console.log(chalk.blue('Dropping generated tables...'));
try {
const backPath = path.join(PROJECT_ROOT, 'back');
process.chdir(backPath);
// Reset migrations (keep base migrations)
execSync('php artisan migrate:reset --force', { stdio: 'pipe' });
execSync('php artisan migrate --force', { stdio: 'pipe' });
console.log(chalk.green(' ✅ Tables reset successfully'));
// Return to original directory
process.chdir(PROJECT_ROOT);
} catch (error) {
console.log(chalk.yellow(' ⚠️ Could not automatically reset tables'));
console.log(chalk.yellow(' 💡 Run manually: cd back && php artisan migrate:reset && php artisan migrate'));
throw error;
}
}
/**
* Restore clean app routes file
* @param {string} filePath - Path to the app.routes.ts file
* @returns {Promise<void>}
*/
async function restoreCleanAppRoutes(filePath) {
const cleanContent = `import { Routes } from '@angular/router';
import { Paths } from './paths';
import { LoginComponent } from './pages/security/login/login.component';
export const routes: Routes = [
{
path: '',
redirectTo: Paths.LOGIN,
pathMatch: 'full'
},
{
path: Paths.LOGIN,
component: LoginComponent
},
// Routes générées automatiquement seront ajoutées ici
];
`;
fs.writeFileSync(filePath, cleanContent);
}
/**
* Restore clean paths file
* @param {string} filePath - Path to the paths.ts file
* @returns {Promise<void>}
*/
async function restoreCleanPaths(filePath) {
const cleanContent = `export enum Paths {
LOGIN = 'login',
DASHBOARD = 'dashboard',
// Paths générés automatiquement seront ajoutés ici
}
`;
fs.writeFileSync(filePath, cleanContent);
}
/**
* Restore clean sidebar file
* @param {string} filePath - Path to the sidebar.component.ts file
* @returns {Promise<void>}
*/
async function restoreCleanSidebar(filePath) {
// Keep the structure but remove generated entries
if (fs.existsSync(filePath)) {
let content = fs.readFileSync(filePath, 'utf8');
// Remove all menu entries containing "this.paths." (generated entries)
// except for the Dashboard entry
// First, let's extract the ngOnInit function
const ngOnInitRegex = /ngOnInit\s*\(\)\s*\{[\s\S]*?\}/;
const ngOnInitMatch = content.match(ngOnInitRegex);
if (ngOnInitMatch) {
// Replace the entire ngOnInit function with our preserved version
const preservedNgOnInit = `ngOnInit() {
this.menuConfig = [
{
label: 'Dashboard',
link: Paths.DASHBOARD,
icon: 'chart-line',
guarded: true,
},
];
}`;
content = content.replace(ngOnInitRegex, preservedNgOnInit);
}
fs.writeFileSync(filePath, content);
}
}
/**
* Restore clean API routes file
* @param {string} filePath - Path to the api.php file
* @returns {Promise<void>}
*/
async function restoreCleanApiRoutes(filePath) {
const cleanContent = `
use Illuminate\\Http\\Request;
use Illuminate\\Support\\Facades\\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
// Routes générées automatiquement seront ajoutées ici
`;
fs.writeFileSync(filePath, cleanContent);
}
/**
* Restore clean service provider file
* @param {string} filePath - Path to the CrudBindingServiceProvider.php file
* @returns {Promise<void>}
*/
async function restoreCleanServiceProvider(filePath) {
const cleanContent = `
namespace App\\Providers;
use Illuminate\\Support\\ServiceProvider;
class CrudBindingServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
// CRUD Bindings - Auto-generated
// Les bindings seront ajoutés automatiquement ici par le générateur CRUD
}
/**
* Bootstrap services.
*/
public function boot(): void
{
//
}
}
`;
fs.writeFileSync(filePath, cleanContent);
}
/**
* Convert string to PascalCase
* @param {string} str - The string to convert
* @returns {string} The PascalCase string
*/
function pascalCase(str) {
return str
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
.replace(/^./, (s) => s.toUpperCase());
}
/**
* Convert string to kebab-case
* @param {string} str - The string to convert
* @returns {string} The kebab-case string
*/
function kebabCase(str) {
return str
.replace(/([a-z])([A-Z])/g, '$1-$2')
.replace(/[\s_]+/g, '-')
.toLowerCase();
}
module.exports = {
getGeneratedComponents,
purgeAngularComponents,
purgeLaravelComponents,
purgeSpecificComponent,
purgeLaravelComponentByName,
cleanAngularRoutes,
cleanAngularSidebar,
cleanLaravelRoutes,
cleanServiceBindings,
cleanSpecificComponentRoutes,
cleanSpecificComponentBindings,
dropGeneratedTables,
restoreCleanAppRoutes,
restoreCleanPaths,
restoreCleanSidebar,
restoreCleanApiRoutes,
restoreCleanServiceProvider,
backupSystemFiles,
restoreSystemFiles,
pascalCase,
kebabCase
};