add force flag to overwrite existing files
This commit is contained in:
parent
a75f286e6b
commit
90cb73379e
@ -8,6 +8,7 @@ import { ModuleUpdaterService } from './services/module-updater.service';
|
|||||||
import { AngularGeneratorService } from './services/angular-generator.service';
|
import { AngularGeneratorService } from './services/angular-generator.service';
|
||||||
import { TemplateService } from './services/template.service';
|
import { TemplateService } from './services/template.service';
|
||||||
import { GenericTableGeneratorService } from './services/generic-table-generator.service';
|
import { GenericTableGeneratorService } from './services/generic-table-generator.service';
|
||||||
|
import { FileSystemService } from './services/file-system.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [],
|
||||||
@ -19,7 +20,8 @@ import { GenericTableGeneratorService } from './services/generic-table-generator
|
|||||||
ModuleUpdaterService,
|
ModuleUpdaterService,
|
||||||
AngularGeneratorService,
|
AngularGeneratorService,
|
||||||
TemplateService,
|
TemplateService,
|
||||||
GenericTableGeneratorService
|
GenericTableGeneratorService,
|
||||||
|
FileSystemService
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
// src/commands/generate.command.ts
|
// src/commands/generate.command.ts
|
||||||
import { Command, CommandRunner } from 'nest-commander';
|
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||||
import { EntityGeneratorService } from '../services/entity-generator.service';
|
import { EntityGeneratorService } from '../services/entity-generator.service';
|
||||||
import { CrudGeneratorService } from '../services/crud-generator.service';
|
import { CrudGeneratorService } from '../services/crud-generator.service';
|
||||||
import { AngularGeneratorService } from '../services/angular-generator.service';
|
import { AngularGeneratorService } from '../services/angular-generator.service';
|
||||||
|
|
||||||
|
interface GenerateCommandOptions {
|
||||||
|
force: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Command({
|
@Command({
|
||||||
name: 'generate',
|
name: 'generate',
|
||||||
description: 'Generate code for the dvbooking project',
|
description: 'Generate code for the dvbooking project',
|
||||||
@ -19,8 +24,9 @@ export class GenerateCommand extends CommandRunner {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(passedParams: string[]): Promise<void> {
|
async run(passedParams: string[], options?: GenerateCommandOptions): Promise<void> {
|
||||||
const [type, name] = passedParams;
|
const [type, name] = passedParams;
|
||||||
|
const force = options?.force || false; // Default force to false
|
||||||
|
|
||||||
if (!type || !name) {
|
if (!type || !name) {
|
||||||
console.error('Error: Missing required arguments: <type> and <name>.');
|
console.error('Error: Missing required arguments: <type> and <name>.');
|
||||||
@ -31,20 +37,21 @@ export class GenerateCommand extends CommandRunner {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'entity':
|
case 'entity':
|
||||||
await this.entityGeneratorService.generate(name);
|
await this.entityGeneratorService.generate(name, force);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'crud':
|
case 'crud':
|
||||||
await this.crudGeneratorService.generate(name);
|
//await this.crudGeneratorService.generate(name,force);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'angular': // <-- 3. Add new case
|
case 'angular':
|
||||||
await this.angularGeneratorService.generate(name);
|
// todo: implement
|
||||||
|
// await this.angularGeneratorService.generate(name,force);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'all':
|
case 'all':
|
||||||
console.log('--- Generating Entity ---');
|
console.log('--- Generating Entity ---');
|
||||||
const columns = await this.entityGeneratorService.generate(name);
|
const columns = await this.entityGeneratorService.generate(name, force);
|
||||||
|
|
||||||
if (!columns) {
|
if (!columns) {
|
||||||
console.error('❌ Entity generation failed. Aborting subsequent steps.');
|
console.error('❌ Entity generation failed. Aborting subsequent steps.');
|
||||||
@ -52,10 +59,10 @@ export class GenerateCommand extends CommandRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('\n--- Generating CRUD Module ---');
|
console.log('\n--- Generating CRUD Module ---');
|
||||||
await this.crudGeneratorService.generate(name, columns);
|
await this.crudGeneratorService.generate(name, columns, force);
|
||||||
console.log('\n✨ All backend files generated successfully! ✨');
|
console.log('\n✨ All backend files generated successfully! ✨');
|
||||||
console.log('\n--- Generating Angular Files ---');
|
console.log('\n--- Generating Angular Files ---');
|
||||||
await this.angularGeneratorService.generate(name,columns);
|
await this.angularGeneratorService.generate(name,columns, force);
|
||||||
console.log('\n✨ All files generated successfully! ✨');
|
console.log('\n✨ All files generated successfully! ✨');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -64,4 +71,14 @@ export class GenerateCommand extends CommandRunner {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NEW: Add the --force option
|
||||||
|
@Option({
|
||||||
|
flags: '-f, --force',
|
||||||
|
description: 'Overwrite existing files.',
|
||||||
|
defaultValue: false,
|
||||||
|
})
|
||||||
|
parseForce(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -9,6 +9,7 @@ import { promises as fsPromises } from 'fs';
|
|||||||
import { TableColumn } from 'typeorm';
|
import { TableColumn } from 'typeorm';
|
||||||
import { GenericTableGeneratorService } from './generic-table-generator.service';
|
import { GenericTableGeneratorService } from './generic-table-generator.service';
|
||||||
import { mapDbToTsType, toCamelCase, toKebabCase, toPascalCase, toSingular } from '../utils/naming.utils';
|
import { mapDbToTsType, toCamelCase, toKebabCase, toPascalCase, toSingular } from '../utils/naming.utils';
|
||||||
|
import { FileSystemService } from './file-system.service';
|
||||||
|
|
||||||
// Interface for structured field metadata passed to templates
|
// Interface for structured field metadata passed to templates
|
||||||
interface FieldDefinition {
|
interface FieldDefinition {
|
||||||
@ -34,9 +35,10 @@ export class AngularGeneratorService {
|
|||||||
private readonly templateService: TemplateService,
|
private readonly templateService: TemplateService,
|
||||||
private readonly moduleUpdaterService: ModuleUpdaterService,
|
private readonly moduleUpdaterService: ModuleUpdaterService,
|
||||||
private readonly genericTableGeneratorService: GenericTableGeneratorService,
|
private readonly genericTableGeneratorService: GenericTableGeneratorService,
|
||||||
|
private readonly fileSystemService: FileSystemService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async generate(tableName: string, columns?: TableColumn[]): Promise<void> {
|
public async generate(tableName: string, columns: TableColumn[], force: boolean): Promise<void> {
|
||||||
console.log(`Generating Angular module for table: ${tableName}...`);
|
console.log(`Generating Angular module for table: ${tableName}...`);
|
||||||
const names = this.getNamingConvention(tableName);
|
const names = this.getNamingConvention(tableName);
|
||||||
|
|
||||||
@ -69,40 +71,40 @@ export class AngularGeneratorService {
|
|||||||
const featureDir = path.join(adminRoot, 'src', 'app', 'features', names.plural);
|
const featureDir = path.join(adminRoot, 'src', 'app', 'features', names.plural);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.generateModel(names, featureDir);
|
await this.generateModel(names, featureDir, force);
|
||||||
await this.generateService(names, featureDir);
|
await this.generateService(names, featureDir, force);
|
||||||
await this.generateFilterComponent(names, featureDir);
|
await this.generateFilterComponent(names, featureDir, force);
|
||||||
await this.generateListComponent(names, featureDir);
|
await this.generateListComponent(names, featureDir, force);
|
||||||
|
const tableCompPath = await this.generateTableComponent(tableName, columns || [], names, featureDir,force);
|
||||||
// 3. New Generic Table View
|
await this.generateDetailsComponent(names, featureDir, force);
|
||||||
const tableCompPath = await this.generateTableComponent(tableName, columns || [], names, featureDir);
|
await this.generateFormComponent(names, featureDir, force);
|
||||||
|
|
||||||
// 4. Details & Form Views
|
|
||||||
await this.generateDetailsComponent(names, featureDir);
|
|
||||||
await this.generateFormComponent(names, featureDir);
|
|
||||||
|
|
||||||
const listCompPath = path.join(featureDir, 'components', `${names.singular}-list`, `${names.singular}-list.component.ts`);
|
const listCompPath = path.join(featureDir, 'components', `${names.singular}-list`, `${names.singular}-list.component.ts`);
|
||||||
const detailsCompPath = path.join(featureDir, 'components', `${names.singular}-details`, `${names.singular}-details.component.ts`);
|
const detailsCompPath = path.join(featureDir, 'components', `${names.singular}-details`, `${names.singular}-details.component.ts`);
|
||||||
const formCompPath = path.join(featureDir, 'components', `${names.singular}-form`, `${names.singular}-form.component.ts`);
|
const formCompPath = path.join(featureDir, 'components', `${names.singular}-form`, `${names.singular}-form.component.ts`);
|
||||||
|
|
||||||
await this.moduleUpdaterService.addRouteToAngularApp(`${names.pascal}FormComponent`, formCompPath, `${names.plural}/new`);
|
// Define the auth object. The role is the kebab-cased singular name (e.g., 'event-type')
|
||||||
await this.moduleUpdaterService.addRouteToAngularApp(`${names.pascal}FormComponent`, formCompPath, `${names.plural}/:id/edit`);
|
const auth = { role: 'admin' };
|
||||||
await this.moduleUpdaterService.addRouteToAngularApp(`${names.pascal}DetailsComponent`, detailsCompPath, `${names.plural}/:id`);
|
|
||||||
|
|
||||||
await this.moduleUpdaterService.addRouteToAngularApp(`${names.pascal}TableComponent`, tableCompPath, `${names.plural}/table`);
|
// Pass the auth object to each route registration call
|
||||||
await this.moduleUpdaterService.addRouteToAngularApp(`${names.pascal}ListComponent`, listCompPath, names.plural);
|
await this.moduleUpdaterService.addRouteToAngularApp(`${names.pascal}FormComponent`, formCompPath, `${names.plural}/new`, auth);
|
||||||
|
await this.moduleUpdaterService.addRouteToAngularApp(`${names.pascal}FormComponent`, formCompPath, `${names.plural}/:id/edit`, auth);
|
||||||
|
await this.moduleUpdaterService.addRouteToAngularApp(`${names.pascal}DetailsComponent`, detailsCompPath, `${names.plural}/:id`, auth);
|
||||||
|
await this.moduleUpdaterService.addRouteToAngularApp(`${names.pascal}TableComponent`, tableCompPath, `${names.plural}/table`, auth);
|
||||||
|
await this.moduleUpdaterService.addRouteToAngularApp(`${names.pascal}ListComponent`, listCompPath, names.plural, auth);
|
||||||
|
// --- END OF FIX ---
|
||||||
|
|
||||||
console.log(`✅ Angular files for "${tableName}" created successfully in: ${featureDir}`);
|
console.log(`✅ Angular files for "${tableName}" created successfully in: ${featureDir}`);
|
||||||
console.log('\n✨ app.routes.ts has been updated with full CRUD routes (List + Table)! ✨');
|
console.log('\n✨ app.routes.ts has been updated with full, secured CRUD routes! ✨');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`❌ An error occurred during Angular generation:`, error.message);
|
console.error(`❌ An error occurred during Angular generation:`, error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async generateTableComponent(tableName: string, columns: TableColumn[], names: NamingConvention, featureDir: string): Promise<string> {
|
private async generateTableComponent(tableName: string, columns: TableColumn[], names: NamingConvention, featureDir: string, force: boolean): Promise<string> {
|
||||||
// This method now correctly calls the renamed service
|
// This method now correctly calls the renamed service
|
||||||
return this.genericTableGeneratorService.generate(tableName, columns, names, featureDir);
|
return this.genericTableGeneratorService.generate(tableName, columns, names, featureDir, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFormFieldHtml(field: FieldDefinition, isFilter: boolean = false): string {
|
private getFormFieldHtml(field: FieldDefinition, isFilter: boolean = false): string {
|
||||||
@ -128,51 +130,51 @@ private async generateTableComponent(tableName: string, columns: TableColumn[],
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ... (All other generate... and naming methods are unchanged)
|
// ... (All other generate... and naming methods are unchanged)
|
||||||
private async generateModel(names: NamingConvention, featureDir: string) {
|
private async generateModel(names: NamingConvention, featureDir: string, force: boolean) {
|
||||||
const modelsDir = path.join(featureDir, 'models');
|
const modelsDir = path.join(featureDir, 'models');
|
||||||
const content = this.templateService.render('angular/model.ts.tpl', names);
|
const content = this.templateService.render('angular/model.ts.tpl', names);
|
||||||
const filePath = path.join(modelsDir, `${names.singular}.model.ts`);
|
const filePath = path.join(modelsDir, `${names.singular}.model.ts`);
|
||||||
await fsPromises.mkdir(modelsDir, { recursive: true });
|
await fsPromises.mkdir(modelsDir, { recursive: true });
|
||||||
fs.writeFileSync(filePath, content);
|
await this.fileSystemService.writeFile(filePath, content, force);
|
||||||
}
|
}
|
||||||
private async generateService(names: NamingConvention, featureDir: string) {
|
private async generateService(names: NamingConvention, featureDir: string, force: boolean) {
|
||||||
const servicesDir = path.join(featureDir, 'services');
|
const servicesDir = path.join(featureDir, 'services');
|
||||||
const content = this.templateService.render('angular/service.ts.tpl', names);
|
const content = this.templateService.render('angular/service.ts.tpl', names);
|
||||||
const filePath = path.join(servicesDir, `${names.singular}.service.ts`);
|
const filePath = path.join(servicesDir, `${names.singular}.service.ts`);
|
||||||
await fsPromises.mkdir(servicesDir, { recursive: true });
|
await fsPromises.mkdir(servicesDir, { recursive: true });
|
||||||
fs.writeFileSync(filePath, content);
|
await this.fileSystemService.writeFile(filePath, content, force);
|
||||||
}
|
}
|
||||||
private async generateFilterComponent(names: NamingConvention, featureDir: string) {
|
private async generateFilterComponent(names: NamingConvention, featureDir: string, force: boolean) {
|
||||||
const compDir = path.join(featureDir, 'components', `${names.singular}-filter`);
|
const compDir = path.join(featureDir, 'components', `${names.singular}-filter`);
|
||||||
const tsContent = this.templateService.render('angular/filter.component.ts.tpl', names);
|
const tsContent = this.templateService.render('angular/filter.component.ts.tpl', names);
|
||||||
const htmlContent = this.templateService.render('angular/filter.component.html.tpl', names);
|
const htmlContent = this.templateService.render('angular/filter.component.html.tpl', names);
|
||||||
await fsPromises.mkdir(compDir, { recursive: true });
|
await fsPromises.mkdir(compDir, { recursive: true });
|
||||||
fs.writeFileSync(path.join(compDir, `${names.singular}-filter.component.ts`), tsContent);
|
await this.fileSystemService.writeFile(path.join(compDir, `${names.singular}-filter.component.ts`), tsContent, force);
|
||||||
fs.writeFileSync(path.join(compDir, `${names.singular}-filter.component.html`), htmlContent);
|
await this.fileSystemService.writeFile(path.join(compDir, `${names.singular}-filter.component.html`), htmlContent, force);
|
||||||
}
|
}
|
||||||
private async generateListComponent(names: NamingConvention, featureDir: string) {
|
private async generateListComponent(names: NamingConvention, featureDir: string, force: boolean) {
|
||||||
const compDir = path.join(featureDir, 'components', `${names.singular}-list`);
|
const compDir = path.join(featureDir, 'components', `${names.singular}-list`);
|
||||||
const tsContent = this.templateService.render('angular/list.component.ts.tpl', names);
|
const tsContent = this.templateService.render('angular/list.component.ts.tpl', names);
|
||||||
const htmlContent = this.templateService.render('angular/list.component.html.tpl', names);
|
const htmlContent = this.templateService.render('angular/list.component.html.tpl', names);
|
||||||
await fsPromises.mkdir(compDir, { recursive: true });
|
await fsPromises.mkdir(compDir, { recursive: true });
|
||||||
fs.writeFileSync(path.join(compDir, `${names.singular}-list.component.ts`), tsContent);
|
await this.fileSystemService.writeFile(path.join(compDir, `${names.singular}-list.component.ts`), tsContent, force);
|
||||||
fs.writeFileSync(path.join(compDir, `${names.singular}-list.component.html`), htmlContent);
|
await this.fileSystemService.writeFile(path.join(compDir, `${names.singular}-list.component.html`), htmlContent, force);
|
||||||
}
|
}
|
||||||
private async generateDetailsComponent(names: NamingConvention, featureDir: string) {
|
private async generateDetailsComponent(names: NamingConvention, featureDir: string, force: boolean) {
|
||||||
const compDir = path.join(featureDir, 'components', `${names.singular}-details`);
|
const compDir = path.join(featureDir, 'components', `${names.singular}-details`);
|
||||||
const tsContent = this.templateService.render('angular/details.component.ts.tpl', names);
|
const tsContent = this.templateService.render('angular/details.component.ts.tpl', names);
|
||||||
const htmlContent = this.templateService.render('angular/details.component.html.tpl', names);
|
const htmlContent = this.templateService.render('angular/details.component.html.tpl', names);
|
||||||
await fsPromises.mkdir(compDir, { recursive: true });
|
await fsPromises.mkdir(compDir, { recursive: true });
|
||||||
fs.writeFileSync(path.join(compDir, `${names.singular}-details.component.ts`), tsContent);
|
await this.fileSystemService.writeFile(path.join(compDir, `${names.singular}-details.component.ts`), tsContent, force);
|
||||||
fs.writeFileSync(path.join(compDir, `${names.singular}-details.component.html`), htmlContent);
|
await this.fileSystemService.writeFile(path.join(compDir, `${names.singular}-details.component.html`), htmlContent, force);
|
||||||
}
|
}
|
||||||
private async generateFormComponent(names: NamingConvention, featureDir: string) {
|
private async generateFormComponent(names: NamingConvention, featureDir: string, force: boolean) {
|
||||||
const compDir = path.join(featureDir, 'components', `${names.singular}-form`);
|
const compDir = path.join(featureDir, 'components', `${names.singular}-form`);
|
||||||
const tsContent = this.templateService.render('angular/form.component.ts.tpl', names);
|
const tsContent = this.templateService.render('angular/form.component.ts.tpl', names);
|
||||||
const htmlContent = this.templateService.render('angular/form.component.html.tpl', names);
|
const htmlContent = this.templateService.render('angular/form.component.html.tpl', names);
|
||||||
await fsPromises.mkdir(compDir, { recursive: true });
|
await fsPromises.mkdir(compDir, { recursive: true });
|
||||||
fs.writeFileSync(path.join(compDir, `${names.singular}-form.component.ts`), tsContent);
|
await this.fileSystemService.writeFile(path.join(compDir, `${names.singular}-form.component.ts`), tsContent, force);
|
||||||
fs.writeFileSync(path.join(compDir, `${names.singular}-form.component.html`), htmlContent);
|
await this.fileSystemService.writeFile(path.join(compDir, `${names.singular}-form.component.html`), htmlContent, force);
|
||||||
}
|
}
|
||||||
private getNamingConvention(tableName: string): NamingConvention {
|
private getNamingConvention(tableName: string): NamingConvention {
|
||||||
const singular = toSingular(tableName);
|
const singular = toSingular(tableName);
|
||||||
|
|||||||
@ -5,9 +5,9 @@ import { ModuleUpdaterService } from './module-updater.service';
|
|||||||
import { TemplateService } from './template.service';
|
import { TemplateService } from './template.service';
|
||||||
import { TableColumn } from 'typeorm';
|
import { TableColumn } from 'typeorm';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as fs from 'fs';
|
|
||||||
import { promises as fsPromises } from 'fs';
|
import { promises as fsPromises } from 'fs';
|
||||||
import { toCamelCase, toKebabCase, toPascalCase, toSingular } from '../utils/naming.utils';
|
import { toCamelCase, toKebabCase, toPascalCase, toSingular } from '../utils/naming.utils';
|
||||||
|
import { FileSystemService } from './file-system.service';
|
||||||
|
|
||||||
interface NamingConvention {
|
interface NamingConvention {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
@ -24,9 +24,10 @@ export class CrudGeneratorService {
|
|||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly moduleUpdaterService: ModuleUpdaterService,
|
private readonly moduleUpdaterService: ModuleUpdaterService,
|
||||||
private readonly templateService: TemplateService,
|
private readonly templateService: TemplateService,
|
||||||
|
private readonly fileSystemService: FileSystemService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async generate(tableName: string, columns?: TableColumn[]): Promise<void> {
|
public async generate(tableName: string, columns: TableColumn[] , force: boolean): Promise<void> {
|
||||||
console.log(`Generating CRUD module for table: ${tableName}...`);
|
console.log(`Generating CRUD module for table: ${tableName}...`);
|
||||||
const names = this.getNamingConvention(tableName, columns);
|
const names = this.getNamingConvention(tableName, columns);
|
||||||
const config = this.configService.get();
|
const config = this.configService.get();
|
||||||
@ -41,10 +42,10 @@ export class CrudGeneratorService {
|
|||||||
names['entityPathForModule'] = path.relative(moduleDir, entityFullPath).replace(/\\/g, '/').replace('.ts', '');
|
names['entityPathForModule'] = path.relative(moduleDir, entityFullPath).replace(/\\/g, '/').replace('.ts', '');
|
||||||
names['entityPathForDtos'] = path.relative(path.join(moduleDir, 'dto'), entityFullPath).replace(/\\/g, '/').replace('.ts', '');
|
names['entityPathForDtos'] = path.relative(path.join(moduleDir, 'dto'), entityFullPath).replace(/\\/g, '/').replace('.ts', '');
|
||||||
|
|
||||||
await this.generateModuleFile(names, moduleDir);
|
await this.generateModuleFile(names, moduleDir, force);
|
||||||
await this.generateControllerFile(names, moduleDir);
|
await this.generateControllerFile(names, moduleDir, force);
|
||||||
await this.generateServiceFile(names, moduleDir);
|
await this.generateServiceFile(names, moduleDir, force);
|
||||||
await this.generateDtoFiles(names, moduleDir);
|
await this.generateDtoFiles(names, moduleDir, force);
|
||||||
|
|
||||||
const moduleFileName = `${names.plural}.module.ts`;
|
const moduleFileName = `${names.plural}.module.ts`;
|
||||||
const fullModulePath = path.join(moduleDir, moduleFileName);
|
const fullModulePath = path.join(moduleDir, moduleFileName);
|
||||||
@ -57,39 +58,39 @@ export class CrudGeneratorService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async generateModuleFile(names: NamingConvention, moduleDir: string) {
|
private async generateModuleFile(names: NamingConvention, moduleDir: string, force: boolean) {
|
||||||
// Use the pre-calculated path
|
// Use the pre-calculated path
|
||||||
const content = this.templateService.render('nestjs/module.ts.tpl', { ...names, entityPath: names['entityPathForModule'] });
|
const content = this.templateService.render('nestjs/module.ts.tpl', { ...names, entityPath: names['entityPathForModule'] });
|
||||||
const filePath = path.join(moduleDir, `${names.plural}.module.ts`);
|
const filePath = path.join(moduleDir, `${names.plural}.module.ts`);
|
||||||
await fsPromises.mkdir(moduleDir, { recursive: true });
|
await fsPromises.mkdir(moduleDir, { recursive: true });
|
||||||
fs.writeFileSync(filePath, content);
|
await this.fileSystemService.writeFile(filePath, content, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async generateControllerFile(names: NamingConvention, moduleDir: string) {
|
private async generateControllerFile(names: NamingConvention, moduleDir: string, force: boolean) {
|
||||||
const content = this.templateService.render('nestjs/controller.ts.tpl', names);
|
const content = this.templateService.render('nestjs/controller.ts.tpl', names);
|
||||||
const filePath = path.join(moduleDir, `${names.plural}.controller.ts`);
|
const filePath = path.join(moduleDir, `${names.plural}.controller.ts`);
|
||||||
await fsPromises.mkdir(moduleDir, { recursive: true });
|
await fsPromises.mkdir(moduleDir, { recursive: true });
|
||||||
fs.writeFileSync(filePath, content);
|
await this.fileSystemService.writeFile(filePath, content, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async generateServiceFile(names: NamingConvention, moduleDir: string) {
|
private async generateServiceFile(names: NamingConvention, moduleDir: string, force: boolean) {
|
||||||
// Use the pre-calculated path
|
// Use the pre-calculated path
|
||||||
const content = this.templateService.render('nestjs/service.ts.tpl', { ...names, entityPath: names['entityPathForModule'] });
|
const content = this.templateService.render('nestjs/service.ts.tpl', { ...names, entityPath: names['entityPathForModule'] });
|
||||||
const filePath = path.join(moduleDir, `${names.plural}.service.ts`);
|
const filePath = path.join(moduleDir, `${names.plural}.service.ts`);
|
||||||
await fsPromises.mkdir(moduleDir, { recursive: true });
|
await fsPromises.mkdir(moduleDir, { recursive: true });
|
||||||
fs.writeFileSync(filePath, content);
|
await this.fileSystemService.writeFile(filePath, content, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async generateDtoFiles(names: NamingConvention, moduleDir: string) {
|
private async generateDtoFiles(names: NamingConvention, moduleDir: string, force: boolean) {
|
||||||
const dtoDir = path.join(moduleDir, 'dto');
|
const dtoDir = path.join(moduleDir, 'dto');
|
||||||
await fsPromises.mkdir(dtoDir, { recursive: true });
|
await fsPromises.mkdir(dtoDir, { recursive: true });
|
||||||
|
|
||||||
// Use the pre-calculated path
|
// Use the pre-calculated path
|
||||||
const dtoNames = { ...names, entityPath: names['entityPathForDtos'] };
|
const dtoNames = { ...names, entityPath: names['entityPathForDtos'] };
|
||||||
|
|
||||||
fs.writeFileSync(path.join(dtoDir, `create-${names.singular}.dto.ts`), this.templateService.render('nestjs/create-dto.ts.tpl', dtoNames));
|
await this.fileSystemService.writeFile(path.join(dtoDir, `create-${names.singular}.dto.ts`), this.templateService.render('nestjs/create-dto.ts.tpl', dtoNames), force);
|
||||||
fs.writeFileSync(path.join(dtoDir, `update-${names.singular}.dto.ts`), this.templateService.render('nestjs/update-dto.ts.tpl', dtoNames));
|
await this.fileSystemService.writeFile(path.join(dtoDir, `update-${names.singular}.dto.ts`), this.templateService.render('nestjs/update-dto.ts.tpl', dtoNames), force);
|
||||||
fs.writeFileSync(path.join(dtoDir, `query-${names.singular}.dto.ts`), this.templateService.render('nestjs/query-dto.ts.tpl', dtoNames));
|
await this.fileSystemService.writeFile(path.join(dtoDir, `query-${names.singular}.dto.ts`), this.templateService.render('nestjs/query-dto.ts.tpl', dtoNames), force);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- NAMING HELPERS ---
|
// --- NAMING HELPERS ---
|
||||||
|
|||||||
@ -8,16 +8,18 @@ import * as path from 'path';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { promises as fsPromises } from 'fs';
|
import { promises as fsPromises } from 'fs';
|
||||||
import { mapDbToTsType, toPascalCase, toSingular } from '../utils/naming.utils';
|
import { mapDbToTsType, toPascalCase, toSingular } from '../utils/naming.utils';
|
||||||
|
import { FileSystemService } from './file-system.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EntityGeneratorService {
|
export class EntityGeneratorService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly moduleUpdaterService: ModuleUpdaterService,
|
private readonly moduleUpdaterService: ModuleUpdaterService,
|
||||||
private readonly templateService: TemplateService, // <-- 2. INJECT TemplateService
|
private readonly templateService: TemplateService,
|
||||||
|
private readonly fileSystemService: FileSystemService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async generate(tableName: string): Promise<TableColumn[] | null> {
|
public async generate(tableName: string, force: boolean): Promise<TableColumn[] | null> {
|
||||||
console.log(`Generating entity for table: ${tableName}...`);
|
console.log(`Generating entity for table: ${tableName}...`);
|
||||||
|
|
||||||
const config = this.configService.get();
|
const config = this.configService.get();
|
||||||
@ -68,7 +70,7 @@ export class EntityGeneratorService {
|
|||||||
const outputPath = path.join(entitiesDir, entityFileName);
|
const outputPath = path.join(entitiesDir, entityFileName);
|
||||||
|
|
||||||
await fsPromises.mkdir(entitiesDir, { recursive: true });
|
await fsPromises.mkdir(entitiesDir, { recursive: true });
|
||||||
fs.writeFileSync(outputPath, entityContent);
|
await this.fileSystemService.writeFile(outputPath, entityContent, force);
|
||||||
console.log(`✅ Entity created successfully at: ${outputPath}`);
|
console.log(`✅ Entity created successfully at: ${outputPath}`);
|
||||||
|
|
||||||
await this.moduleUpdaterService.addEntityToTypeOrm(className, outputPath);
|
await this.moduleUpdaterService.addEntityToTypeOrm(className, outputPath);
|
||||||
|
|||||||
41
src/services/file-system.service.ts
Normal file
41
src/services/file-system.service.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// src/services/file-system.service.ts
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { promises as fsPromises } from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FileSystemService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes content to a file, but only if the file does not already exist,
|
||||||
|
* or if the 'force' flag is set to true.
|
||||||
|
*
|
||||||
|
* @param filePath The full path to the file.
|
||||||
|
* @param content The content to write.
|
||||||
|
* @param force If true, overwrites the file even if it exists.
|
||||||
|
*/
|
||||||
|
public async writeFile(filePath: string, content: string, force: boolean): Promise<void> {
|
||||||
|
if (!force && fs.existsSync(filePath)) {
|
||||||
|
console.warn(`⚠️ SKIPPED: File already exists at ${filePath}. Use --force to overwrite.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ensure the directory exists before writing the file
|
||||||
|
const dir = path.dirname(filePath);
|
||||||
|
await fsPromises.mkdir(dir, { recursive: true });
|
||||||
|
|
||||||
|
fs.writeFileSync(filePath, content);
|
||||||
|
|
||||||
|
if (force && fs.existsSync(filePath)) {
|
||||||
|
console.log(`✅ OVERWRITTEN: ${filePath}`);
|
||||||
|
} else {
|
||||||
|
console.log(`✅ CREATED: ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ FAILED to write file at ${filePath}:`, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,19 +6,22 @@ import * as path from 'path';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { promises as fsPromises } from 'fs';
|
import { promises as fsPromises } from 'fs';
|
||||||
import { mapDbToTsType } from '../utils/naming.utils';
|
import { mapDbToTsType } from '../utils/naming.utils';
|
||||||
|
import { FileSystemService } from './file-system.service';
|
||||||
|
|
||||||
interface FieldDefinition { name: string; tsType: string; }
|
interface FieldDefinition { name: string; tsType: string; }
|
||||||
interface NamingConvention { [key: string]: string; /*...*/ }
|
interface NamingConvention { [key: string]: string; /*...*/ }
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GenericTableGeneratorService {
|
export class GenericTableGeneratorService {
|
||||||
constructor(private readonly templateService: TemplateService) {}
|
constructor(private readonly templateService: TemplateService,
|
||||||
|
private readonly fileSystemService: FileSystemService,) {}
|
||||||
|
|
||||||
public async generate(
|
public async generate(
|
||||||
tableName: string,
|
tableName: string,
|
||||||
columns: TableColumn[],
|
columns: TableColumn[],
|
||||||
names: NamingConvention,
|
names: NamingConvention,
|
||||||
featureDir: string,
|
featureDir: string,
|
||||||
|
force: boolean
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
console.log(`Generating Generic Table View for ${tableName}...`);
|
console.log(`Generating Generic Table View for ${tableName}...`);
|
||||||
|
|
||||||
@ -34,21 +37,24 @@ export class GenericTableGeneratorService {
|
|||||||
await fsPromises.mkdir(compDir, { recursive: true });
|
await fsPromises.mkdir(compDir, { recursive: true });
|
||||||
|
|
||||||
// Generate Data Provider
|
// Generate Data Provider
|
||||||
fs.writeFileSync(
|
await this.fileSystemService.writeFile(
|
||||||
path.join(compDir, `${names.singular}-data-provider.service.ts`),
|
path.join(compDir, `${names.singular}-data-provider.service.ts`),
|
||||||
this.templateService.render('angular-generic/data-provider.service.ts.tpl', names),
|
this.templateService.render('angular-generic/data-provider.service.ts.tpl', names),
|
||||||
|
force
|
||||||
);
|
);
|
||||||
|
|
||||||
// Generate Table Component TS
|
// Generate Table Component TS
|
||||||
fs.writeFileSync(
|
await this.fileSystemService.writeFile(
|
||||||
path.join(compDir, `${names.singular}-table.component.ts`),
|
path.join(compDir, `${names.singular}-table.component.ts`),
|
||||||
this.templateService.render('angular-generic/table.component.ts.tpl', names),
|
this.templateService.render('angular-generic/table.component.ts.tpl', names),
|
||||||
|
force
|
||||||
);
|
);
|
||||||
|
|
||||||
// Generate Table Component HTML
|
// Generate Table Component HTML
|
||||||
fs.writeFileSync(
|
await this.fileSystemService.writeFile(
|
||||||
path.join(compDir, `${names.singular}-table.component.html`),
|
path.join(compDir, `${names.singular}-table.component.html`),
|
||||||
this.templateService.render('angular-generic/table.component.html.tpl', names),
|
this.templateService.render('angular-generic/table.component.html.tpl', names),
|
||||||
|
force
|
||||||
);
|
);
|
||||||
|
|
||||||
return path.join(compDir, `${names.singular}-table.component.ts`);
|
return path.join(compDir, `${names.singular}-table.component.ts`);
|
||||||
|
|||||||
@ -115,6 +115,7 @@ export class ModuleUpdaterService {
|
|||||||
componentNameToAdd: string,
|
componentNameToAdd: string,
|
||||||
componentPath: string,
|
componentPath: string,
|
||||||
routeName: string,
|
routeName: string,
|
||||||
|
auth?: { role: string } // NEW: Optional auth parameter
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const config = this.configService.get();
|
const config = this.configService.get();
|
||||||
const adminRoot = path.resolve(process.cwd(), config.admin.path);
|
const adminRoot = path.resolve(process.cwd(), config.admin.path);
|
||||||
@ -126,37 +127,48 @@ export class ModuleUpdaterService {
|
|||||||
const project = new Project();
|
const project = new Project();
|
||||||
const sourceFile = project.addSourceFileAtPath(appRoutesPath);
|
const sourceFile = project.addSourceFileAtPath(appRoutesPath);
|
||||||
|
|
||||||
const relativeComponentPath = path
|
// 1. Add Component Import (unchanged)
|
||||||
.relative(path.dirname(appRoutesPath), componentPath)
|
const relativeComponentPath = path.relative(path.dirname(appRoutesPath), componentPath).replace(/\\/g, '/').replace('.ts', '');
|
||||||
.replace(/\\/g, '/')
|
const existingCompImport = sourceFile.getImportDeclaration(d => d.getModuleSpecifierValue() === `./${relativeComponentPath}`);
|
||||||
.replace('.ts', '');
|
if (!existingCompImport) {
|
||||||
|
sourceFile.addImportDeclaration({ namedImports: [componentNameToAdd], moduleSpecifier: `./${relativeComponentPath}` });
|
||||||
const existingImport = sourceFile.getImportDeclaration(
|
|
||||||
(d) => d.getModuleSpecifierValue() === `./${relativeComponentPath}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!existingImport) {
|
|
||||||
sourceFile.addImportDeclaration({
|
|
||||||
namedImports: [componentNameToAdd],
|
|
||||||
moduleSpecifier: `./${relativeComponentPath}`,
|
|
||||||
});
|
|
||||||
console.log(`Added import for component ${componentNameToAdd}.`);
|
console.log(`Added import for component ${componentNameToAdd}.`);
|
||||||
} else {
|
|
||||||
console.log(`Import for component ${componentNameToAdd} already exists.`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Add AuthGuard Import if needed
|
||||||
|
if (auth) {
|
||||||
|
// NOTE: This assumes a specific path for your AuthGuard. Adjust if necessary.
|
||||||
|
const authGuardPath = './auth/auth.guard';
|
||||||
|
const existingAuthImport = sourceFile.getImportDeclaration(d => d.getModuleSpecifierValue() === authGuardPath);
|
||||||
|
if (!existingAuthImport) {
|
||||||
|
sourceFile.addImportDeclaration({ namedImports: ['AuthGuard'], moduleSpecifier: authGuardPath });
|
||||||
|
console.log('Added import for AuthGuard.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Find routes array (unchanged)
|
||||||
const routesDeclaration = sourceFile.getVariableDeclarationOrThrow('routes');
|
const routesDeclaration = sourceFile.getVariableDeclarationOrThrow('routes');
|
||||||
const routesArray = routesDeclaration.getInitializerIfKindOrThrow(
|
const routesArray = routesDeclaration.getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
||||||
SyntaxKind.ArrayLiteralExpression,
|
|
||||||
);
|
|
||||||
|
|
||||||
const routeAlreadyExists = routesArray
|
// 4. Build the Route Object String
|
||||||
.getElements()
|
let routeObjectString = `{ path: '${routeName}', component: ${componentNameToAdd} }`;
|
||||||
.some((elem) => elem.getText().includes(`path: '${routeName}'`));
|
if (auth) {
|
||||||
|
// If auth is requested, build a multi-line string for the route object
|
||||||
|
routeObjectString = `{
|
||||||
|
path: '${routeName}',
|
||||||
|
component: ${componentNameToAdd},
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
data: {
|
||||||
|
roles: ['${auth.role}'],
|
||||||
|
},
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Check existence and insert the route
|
||||||
|
const routeAlreadyExists = routesArray.getElements().some((elem) => elem.getText().includes(`path: '${routeName}'`));
|
||||||
if (!routeAlreadyExists) {
|
if (!routeAlreadyExists) {
|
||||||
routesArray.insertElement(0, `{ path: '${routeName}', component: ${componentNameToAdd} }`);
|
routesArray.insertElement(0, routeObjectString);
|
||||||
console.log(`Added route for path '${routeName}' to the beginning of the routes array.`);
|
console.log(`Added route for path '${routeName}' with auth config.`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`Route for path '${routeName}' already exists.`);
|
console.log(`Route for path '${routeName}' already exists.`);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user