nestjs-reverse-engineering
Version:
A powerful TypeScript/NestJS library for database reverse engineering, entity generation, and CRUD operations
532 lines (531 loc) ⢠21.3 kB
JavaScript
#!/usr/bin/env node
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const reverse_engineering_service_new_1 = require("../src/reverse-engineering/reverse-engineering-service-new");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const program = new commander_1.Command();
// Load configuration from file if exists
function loadConfig() {
const configPaths = [
'reverse-engineering.config.js',
'reverse-engineering.config.json',
'.reverserc.js',
'.reverserc.json'
];
for (const configPath of configPaths) {
if (fs.existsSync(configPath)) {
console.log(`š Loading configuration from ${configPath}`);
try {
let config;
if (configPath.endsWith('.json')) {
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
}
else {
delete require.cache[require.resolve(path.resolve(configPath))];
config = require(path.resolve(configPath));
}
// Ensure database password is a string
if (config.database && config.database.password !== undefined) {
config.database.password = String(config.database.password);
}
return config;
}
catch (error) {
console.warn(`ā ļø Failed to load config from ${configPath}:`, error.message);
}
}
}
return null;
}
// Create database configuration from CLI options
function createDatabaseConfig(options) {
const config = {};
// Only add properties that are actually provided via CLI (not defaults)
if (options.dialect && options.dialect !== 'postgres')
config.type = options.dialect;
if (options.host && options.host !== 'localhost')
config.host = options.host;
if (options.port)
config.port = parseInt(options.port);
if (options.username || options.user)
config.username = options.username || options.user;
if (options.password)
config.password = String(options.password);
if (options.database)
config.database = options.database;
if (options.schema)
config.schema = options.schema;
if (options.ssl)
config.ssl = options.ssl;
return config;
}
// Main program configuration
program
.name('nestjs-reverse-engineering')
.description('A powerful reverse engineering and CRUD generation library for NestJS/TypeScript')
.version('1.0.0');
// Global options
program
.option('-h, --host <host>', 'Database host')
.option('-p, --port <port>', 'Database port')
.option('-u, --username <username>', 'Database username')
.option('-w, --password <password>', 'Database password')
.option('-d, --database <database>', 'Database name')
.option('-s, --schema <schema>', 'Database schema')
.option('--dialect <dialect>', 'Database dialect (postgres|mysql)')
.option('--ssl', 'Use SSL connection')
.option('--config <path>', 'Configuration file path')
.option('--output-dir <dir>', 'Base output directory')
.option('--entities-dir <dir>', 'Entities directory')
.option('--sql-dir <dir>', 'SQL output directory')
.option('--data-dir <dir>', 'Data export directory');
// Command: Generate entities
program
.command('generate:entities')
.alias('entities')
.description('Generate TypeORM entities from database schema')
.option('--include-tables <tables>', 'Comma-separated list of tables to include')
.option('--exclude-tables <tables>', 'Comma-separated list of tables to exclude')
.option('--no-validation', 'Skip validation decorators')
.option('--no-swagger', 'Skip Swagger decorators')
.option('--no-index', 'Skip index file generation')
.action(async (options, command) => {
try {
const globalOptions = command.parent.opts();
const fileConfig = loadConfig() || {};
const config = {
...fileConfig,
database: {
...fileConfig.database,
...createDatabaseConfig(globalOptions)
},
paths: {
baseOutput: globalOptions.outputDir || fileConfig.paths?.baseOutput || './src',
entities: globalOptions.entitiesDir || fileConfig.paths?.entities || './src/entities',
crud: globalOptions.outputDir || fileConfig.paths?.crud || './src',
sql: globalOptions.sqlDir || fileConfig.paths?.sql || './sql',
dataExport: globalOptions.dataDir || fileConfig.paths?.dataExport || './data'
},
features: {
entities: true,
crud: false,
sql: false,
dataExport: false,
generateIndex: !options.noIndex,
...fileConfig.features
},
entities: {
includeValidation: !options.noValidation,
includeSwagger: !options.noSwagger,
useTypeORM: true,
generateEnums: true,
excludedTables: options.excludeTables ? options.excludeTables.split(',') : [],
includedTables: options.includeTables ? options.includeTables.split(',') : undefined,
...fileConfig.entities
}
};
const service = new reverse_engineering_service_new_1.ReverseEngineeringService();
service.initialize(config);
await service.generateEntities();
}
catch (error) {
console.error('ā Entity generation failed:', error.message);
process.exit(1);
}
});
// Command: Generate CRUD
program
.command('generate:crud')
.alias('crud')
.description('Generate CRUD operations (controllers, services, repositories)')
.option('--framework <framework>', 'Framework to use (nestjs|express|fastify)', 'nestjs')
.option('--include-tables <tables>', 'Comma-separated list of tables to include')
.option('--exclude-tables <tables>', 'Comma-separated list of tables to exclude')
.option('--no-validation', 'Skip validation decorators')
.option('--no-swagger', 'Skip Swagger decorators')
.option('--no-pagination', 'Skip pagination support')
.option('--no-filtering', 'Skip filtering support')
.option('--no-sorting', 'Skip sorting support')
.option('--no-dto', 'Skip DTO generation')
.option('--auth', 'Include authentication guards')
.option('--tests', 'Generate test files')
.action(async (options, command) => {
try {
const globalOptions = command.parent.opts();
const fileConfig = loadConfig() || {};
const config = {
...fileConfig,
database: {
...fileConfig.database,
...createDatabaseConfig(globalOptions)
},
paths: {
baseOutput: globalOptions.outputDir || fileConfig.paths?.baseOutput || './src',
entities: globalOptions.entitiesDir || fileConfig.paths?.entities || './src/entities',
crud: globalOptions.outputDir || fileConfig.paths?.crud || './src',
sql: globalOptions.sqlDir || fileConfig.paths?.sql || './sql',
dataExport: globalOptions.dataDir || fileConfig.paths?.dataExport || './data'
},
features: {
entities: false,
crud: true,
sql: false,
dataExport: false,
generateIndex: false,
...fileConfig.features
},
crud: {
framework: options.framework,
includeValidation: !options.noValidation,
includeSwagger: !options.noSwagger,
includePagination: !options.noPagination,
includeFiltering: !options.noFiltering,
includeSorting: !options.noSorting,
includeRelations: false,
generateTests: options.tests,
authGuards: options.auth,
useTypeORM: true,
useDTO: !options.noDto,
excludedTables: options.excludeTables ? options.excludeTables.split(',') : [],
includedTables: options.includeTables ? options.includeTables.split(',') : undefined,
...fileConfig.crud
}
};
const service = new reverse_engineering_service_new_1.ReverseEngineeringService();
service.initialize(config);
await service.generateCrud();
}
catch (error) {
console.error('ā CRUD generation failed:', error.message);
process.exit(1);
}
});
// Command: Generate SQL scripts
program
.command('generate:sql')
.alias('sql')
.description('Generate SQL scripts (CREATE TABLE, INSERT statements)')
.option('--no-create', 'Skip CREATE TABLE scripts')
.option('--no-insert', 'Skip INSERT scripts')
.option('--no-format', 'Skip SQL formatting')
.action(async (options, command) => {
try {
const globalOptions = command.parent.opts();
const fileConfig = loadConfig() || {};
const config = {
...fileConfig,
database: {
...fileConfig.database,
...createDatabaseConfig(globalOptions)
},
paths: {
baseOutput: globalOptions.outputDir || fileConfig.paths?.baseOutput || './src',
entities: globalOptions.entitiesDir || fileConfig.paths?.entities || './src/entities',
crud: globalOptions.outputDir || fileConfig.paths?.crud || './src',
sql: globalOptions.sqlDir || fileConfig.paths?.sql || './sql',
dataExport: globalOptions.dataDir || fileConfig.paths?.dataExport || './data'
},
features: {
entities: false,
crud: false,
sql: true,
dataExport: false,
generateIndex: false,
...fileConfig.features
},
sql: {
generateCreateTables: !options.noCreate,
generateInserts: !options.noInsert,
formatSql: !options.noFormat,
includeComments: true,
...fileConfig.sql
}
};
const service = new reverse_engineering_service_new_1.ReverseEngineeringService();
service.initialize(config);
await service.generateSql();
}
catch (error) {
console.error('ā SQL generation failed:', error.message);
process.exit(1);
}
});
// Command: Export data
program
.command('export:data')
.alias('data')
.description('Export database data with optional masking')
.option('--format <format>', 'Export format (sql|json|csv)', 'sql')
.option('--batch-size <size>', 'Batch size for exports', '1000')
.option('--max-rows <rows>', 'Maximum rows per table')
.option('--mask', 'Enable data masking')
.option('--mask-fields <fields>', 'Comma-separated list of fields to mask')
.action(async (options, command) => {
try {
const globalOptions = command.parent.opts();
const fileConfig = loadConfig() || {};
const config = {
...fileConfig,
database: {
...fileConfig.database,
...createDatabaseConfig(globalOptions)
},
paths: {
baseOutput: globalOptions.outputDir || fileConfig.paths?.baseOutput || './src',
entities: globalOptions.entitiesDir || fileConfig.paths?.entities || './src/entities',
crud: globalOptions.outputDir || fileConfig.paths?.crud || './src',
sql: globalOptions.sqlDir || fileConfig.paths?.sql || './sql',
dataExport: globalOptions.dataDir || fileConfig.paths?.dataExport || './data'
},
features: {
entities: false,
crud: false,
sql: false,
dataExport: true,
generateIndex: false,
...fileConfig.features
},
dataExport: {
enableMasking: options.mask,
batchSize: parseInt(options.batchSize) || 1000,
format: options.format,
maskedFields: options.maskFields ? options.maskFields.split(',') :
['password', 'email', 'phone', 'ssn', 'credit_card'],
maxRows: options.maxRows ? parseInt(options.maxRows) : undefined,
...fileConfig.dataExport
}
};
const service = new reverse_engineering_service_new_1.ReverseEngineeringService();
service.initialize(config);
await service.exportData();
}
catch (error) {
console.error('ā Data export failed:', error.message);
process.exit(1);
}
});
// Command: Generate all
program
.command('generate:all')
.alias('all')
.description('Generate everything (entities, CRUD, SQL, data export)')
.option('--skip-entities', 'Skip entity generation')
.option('--skip-crud', 'Skip CRUD generation')
.option('--skip-sql', 'Skip SQL generation')
.option('--skip-data', 'Skip data export')
.action(async (options, command) => {
try {
const globalOptions = command.parent.opts();
const fileConfig = loadConfig() || {};
const config = {
...fileConfig,
database: {
...fileConfig.database,
...createDatabaseConfig(globalOptions)
},
paths: {
baseOutput: globalOptions.outputDir || fileConfig.paths?.baseOutput || './src',
entities: globalOptions.entitiesDir || fileConfig.paths?.entities || './src/entities',
crud: fileConfig.paths?.crud || globalOptions.outputDir || './src',
sql: globalOptions.sqlDir || fileConfig.paths?.sql || './sql',
dataExport: globalOptions.dataDir || fileConfig.paths?.dataExport || './data'
},
features: {
...fileConfig.features,
entities: fileConfig.features?.entities !== false && !options.skipEntities,
crud: fileConfig.features?.crud !== false && !options.skipCrud,
sql: fileConfig.features?.sql !== false && !options.skipSql,
dataExport: fileConfig.features?.dataExport !== false && !options.skipData,
generateIndex: fileConfig.features?.generateIndex !== false && !options.skipEntities
}
};
const service = new reverse_engineering_service_new_1.ReverseEngineeringService();
service.initialize(config);
await service.generateAll();
}
catch (error) {
console.error('ā Generation failed:', error.message);
process.exit(1);
}
});
// Command: Test connection
program
.command('test')
.description('Test database connection')
.action(async (options, command) => {
try {
const globalOptions = command.parent.opts();
const fileConfig = loadConfig() || {};
const dbConfig = {
...fileConfig.database,
...createDatabaseConfig(globalOptions)
};
const service = new reverse_engineering_service_new_1.ReverseEngineeringService();
const success = await service.testConnection(dbConfig);
if (!success) {
process.exit(1);
}
}
catch (error) {
console.error('ā Connection test failed:', error.message);
process.exit(1);
}
});
// Command: List tables
program
.command('tables')
.description('List database tables')
.action(async (options, command) => {
try {
const globalOptions = command.parent.opts();
const fileConfig = loadConfig() || {};
const dbConfig = {
...fileConfig.database,
...createDatabaseConfig(globalOptions)
};
const service = new reverse_engineering_service_new_1.ReverseEngineeringService();
const tables = await service.getTables(dbConfig);
console.log(`\nš Found ${tables.length} tables:\n`);
tables.forEach((table, index) => {
console.log(`${index + 1}. ${table.tableName} (${table.columns.length} columns)`);
});
}
catch (error) {
console.error('ā Failed to list tables:', error.message);
process.exit(1);
}
});
// Command: Generate config file
program
.command('init')
.description('Generate configuration file')
.option('--format <format>', 'Config format (js|json)', 'js')
.action(async (options) => {
try {
const configTemplate = {
database: {
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'password',
database: 'your_database',
schema: 'public'
},
paths: {
baseOutput: './src',
entities: './src/entities',
crud: './src',
sql: './sql',
dataExport: './data'
},
features: {
entities: true,
crud: true,
sql: true,
dataExport: false,
generateIndex: true
},
crud: {
framework: 'nestjs',
includeValidation: true,
includeSwagger: true,
includePagination: true,
includeFiltering: true,
includeSorting: true,
includeRelations: false,
generateTests: false,
authGuards: false,
useTypeORM: true,
useDTO: true,
excludedTables: [],
includedTables: undefined
},
entities: {
useTypeORM: true,
includeValidation: true,
includeSwagger: true,
generateEnums: true,
excludedTables: [],
includedTables: undefined
},
sql: {
generateCreateTables: true,
generateInserts: true,
formatSql: true,
includeComments: true
},
dataExport: {
enableMasking: false,
batchSize: 1000,
format: 'sql',
maskedFields: ['password', 'email', 'phone', 'ssn', 'credit_card'],
maxRows: undefined
}
};
const filename = options.format === 'json' ?
'reverse-engineering.config.json' :
'reverse-engineering.config.js';
let content;
if (options.format === 'json') {
content = JSON.stringify(configTemplate, null, 2);
}
else {
content = `module.exports = ${JSON.stringify(configTemplate, null, 2)};`;
}
fs.writeFileSync(filename, content);
console.log(`ā
Configuration file created: ${filename}`);
console.log('š Edit the configuration file to match your setup');
}
catch (error) {
console.error('ā Failed to create config file:', error.message);
process.exit(1);
}
});
// Handle unknown commands
program.on('command:*', () => {
console.error('ā Invalid command: %s', program.args.join(' '));
console.log('See --help for a list of available commands.');
process.exit(1);
});
// Show help if no command is provided
if (!process.argv.slice(2).length) {
program.outputHelp();
process.exit(0);
}
program.parse(process.argv);