eva4j 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/QUICK_REFERENCE.md +204 -0
- package/README.md +912 -0
- package/USAGE.md +349 -0
- package/bin/eva4j.js +234 -0
- package/config/defaults.json +46 -0
- package/package.json +57 -0
- package/src/commands/add-kafka-client.js +193 -0
- package/src/commands/add-module.js +221 -0
- package/src/commands/create.js +92 -0
- package/src/commands/detach.js +495 -0
- package/src/commands/generate-http-exchange.js +309 -0
- package/src/commands/generate-kafka-event.js +453 -0
- package/src/commands/generate-kafka-listener.js +267 -0
- package/src/commands/generate-resource.js +265 -0
- package/src/commands/generate-usecase.js +198 -0
- package/src/commands/info.js +63 -0
- package/src/generators/base-generator.js +150 -0
- package/src/generators/module-generator.js +48 -0
- package/src/generators/shared-generator.js +153 -0
- package/src/utils/config-manager.js +156 -0
- package/src/utils/context-builder.js +149 -0
- package/src/utils/naming.js +137 -0
- package/src/utils/template-engine.js +55 -0
- package/src/utils/validator.js +159 -0
- package/templates/base/application/Application.java.ejs +27 -0
- package/templates/base/docker/docker-compose.yml.ejs +41 -0
- package/templates/base/gradle/build.gradle.ejs +70 -0
- package/templates/base/gradle/settings.gradle.ejs +1 -0
- package/templates/base/resources/application-develop.yml.ejs +5 -0
- package/templates/base/resources/application-local.yml.ejs +5 -0
- package/templates/base/resources/application-production.yml.ejs +9 -0
- package/templates/base/resources/application-test.yml.ejs +5 -0
- package/templates/base/resources/application.yml.ejs +31 -0
- package/templates/base/resources/parameters/develop/cors.yml.ejs +4 -0
- package/templates/base/resources/parameters/develop/db.yaml.ejs +21 -0
- package/templates/base/resources/parameters/develop/kafka.yml.ejs +26 -0
- package/templates/base/resources/parameters/local/cors.yml.ejs +4 -0
- package/templates/base/resources/parameters/local/db.yaml.ejs +21 -0
- package/templates/base/resources/parameters/local/kafka.yml.ejs +26 -0
- package/templates/base/resources/parameters/production/cors.yml.ejs +4 -0
- package/templates/base/resources/parameters/production/db.yaml.ejs +21 -0
- package/templates/base/resources/parameters/production/kafka.yml.ejs +26 -0
- package/templates/base/root/README.md.ejs +126 -0
- package/templates/base/root/gitignore.ejs +42 -0
- package/templates/http-exchange/Adapter.java.ejs +39 -0
- package/templates/http-exchange/Config.java.ejs +24 -0
- package/templates/http-exchange/FeignClient.java.ejs +23 -0
- package/templates/http-exchange/Port.java.ejs +14 -0
- package/templates/kafka-event/Event.java.ejs +10 -0
- package/templates/kafka-event/KafkaConfigBean.java.ejs +7 -0
- package/templates/kafka-event/KafkaMessageBroker.java.ejs +32 -0
- package/templates/kafka-event/MessageBroker.java.ejs +8 -0
- package/templates/kafka-event/MessageBrokerImplMethod.java.ejs +9 -0
- package/templates/kafka-event/MessageBrokerMethod.java.ejs +1 -0
- package/templates/kafka-listener/KafkaController.java.ejs +34 -0
- package/templates/kafka-listener/ListenerMethod.java.ejs +9 -0
- package/templates/kafka-listener/ValueField.java.ejs +4 -0
- package/templates/module/controller.java.ejs +96 -0
- package/templates/module/exception.java.ejs +18 -0
- package/templates/module/mapper.java.ejs +67 -0
- package/templates/module/model.java.ejs +29 -0
- package/templates/module/package-info.java.ejs +7 -0
- package/templates/module/repository.java.ejs +26 -0
- package/templates/module/request-dto.java.ejs +24 -0
- package/templates/module/response-dto.java.ejs +26 -0
- package/templates/module/service-impl.java.ejs +112 -0
- package/templates/module/service.java.ejs +45 -0
- package/templates/module/update-dto.java.ejs +25 -0
- package/templates/resource/Command.java.ejs +9 -0
- package/templates/resource/CommandHandler.java.ejs +22 -0
- package/templates/resource/Controller.java.ejs +73 -0
- package/templates/resource/Query.java.ejs +12 -0
- package/templates/resource/QueryHandler.java.ejs +31 -0
- package/templates/resource/ResponseDto.java.ejs +6 -0
- package/templates/shared/annotations/ApplicationComponent.java.ejs +9 -0
- package/templates/shared/annotations/DomainComponent.java.ejs +9 -0
- package/templates/shared/annotations/LogAfter.java.ejs +11 -0
- package/templates/shared/annotations/LogBefore.java.ejs +11 -0
- package/templates/shared/annotations/LogExceptions.java.ejs +11 -0
- package/templates/shared/annotations/LogTimer.java.ejs +11 -0
- package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +49 -0
- package/templates/shared/configurations/loggerConfig/HandlerLogs.java.ejs +56 -0
- package/templates/shared/configurations/securityConfig/SecurityConfig.java.ejs +57 -0
- package/templates/shared/configurations/swaggerConfig/SwaggerConfig.java.ejs +31 -0
- package/templates/shared/configurations/useCaseConfig/UseCaseAutoRegister.java.ejs +51 -0
- package/templates/shared/configurations/useCaseConfig/UseCaseConfig.java.ejs +25 -0
- package/templates/shared/configurations/useCaseConfig/UseCaseContainer.java.ejs +29 -0
- package/templates/shared/configurations/useCaseConfig/UseCaseMediator.java.ejs +38 -0
- package/templates/shared/customExceptions/BadRequestException.java.ejs +11 -0
- package/templates/shared/customExceptions/ConflictException.java.ejs +8 -0
- package/templates/shared/customExceptions/ForbiddenException.java.ejs +8 -0
- package/templates/shared/customExceptions/ImportFileException.java.ejs +6 -0
- package/templates/shared/customExceptions/NotFoundException.java.ejs +8 -0
- package/templates/shared/customExceptions/UnauthorizedException.java.ejs +9 -0
- package/templates/shared/customExceptions/ValidationException.java.ejs +17 -0
- package/templates/shared/errorMessage/ErrorMessage.java.ejs +5 -0
- package/templates/shared/errorMessage/FullErrorMessage.java.ejs +9 -0
- package/templates/shared/errorMessage/ShortErrorMessage.java.ejs +6 -0
- package/templates/shared/eventEnvelope/EventEnvelope.java.ejs +13 -0
- package/templates/shared/eventEnvelope/EventMetadata.java.ejs +24 -0
- package/templates/shared/filters/CorrelationIdFilter.java.ejs +45 -0
- package/templates/shared/handlerException/HandlerExceptions.java.ejs +148 -0
- package/templates/shared/interfaces/Command.java.ejs +4 -0
- package/templates/shared/interfaces/CommandHandler.java.ejs +5 -0
- package/templates/shared/interfaces/Dispatchable.java.ejs +4 -0
- package/templates/shared/interfaces/Handler.java.ejs +4 -0
- package/templates/shared/interfaces/Query.java.ejs +4 -0
- package/templates/shared/interfaces/QueryHandler.java.ejs +5 -0
- package/templates/shared/package-info.java.ejs +8 -0
- package/templates/usecase/command/Command.java.ejs +7 -0
- package/templates/usecase/command/CommandHandler.java.ejs +21 -0
- package/templates/usecase/query/Query.java.ejs +10 -0
- package/templates/usecase/query/QueryHandler.java.ejs +22 -0
- package/templates/usecase/query/ResponseDto.java.ejs +5 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class ConfigManager {
|
|
5
|
+
constructor(projectPath = process.cwd()) {
|
|
6
|
+
this.projectPath = projectPath;
|
|
7
|
+
this.configFile = path.join(projectPath, '.eva4j.json');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Save project configuration to .eva4j.json
|
|
12
|
+
* @param {Object} config - Project configuration
|
|
13
|
+
* @param {string} config.projectName - Name of the project
|
|
14
|
+
* @param {string} config.groupId - Maven group ID
|
|
15
|
+
* @param {string} config.artifactId - Maven artifact ID
|
|
16
|
+
* @param {string} config.packageName - Base package name
|
|
17
|
+
* @param {string} config.javaVersion - Java version
|
|
18
|
+
* @param {string} config.springBootVersion - Spring Boot version
|
|
19
|
+
* @param {string} config.springModulithVersion - Spring Modulith version
|
|
20
|
+
* @param {Array<string>} config.dependencies - Selected dependencies
|
|
21
|
+
* @param {string} config.createdAt - ISO timestamp of creation
|
|
22
|
+
*/
|
|
23
|
+
async saveProjectConfig(config) {
|
|
24
|
+
const projectConfig = {
|
|
25
|
+
projectName: config.projectName,
|
|
26
|
+
groupId: config.groupId,
|
|
27
|
+
artifactId: config.artifactId,
|
|
28
|
+
packageName: config.packageName,
|
|
29
|
+
javaVersion: config.javaVersion,
|
|
30
|
+
springBootVersion: config.springBootVersion,
|
|
31
|
+
springModulithVersion: config.springModulithVersion,
|
|
32
|
+
dependencies: config.dependencies || [],
|
|
33
|
+
databaseType: config.databaseType,
|
|
34
|
+
modules: [],
|
|
35
|
+
createdAt: config.createdAt || new Date().toISOString(),
|
|
36
|
+
updatedAt: new Date().toISOString()
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
await fs.writeJson(this.configFile, projectConfig, { spaces: 2 });
|
|
40
|
+
return projectConfig;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load project configuration from .eva4j.json
|
|
45
|
+
* @returns {Object|null} Project configuration or null if not found
|
|
46
|
+
*/
|
|
47
|
+
async loadProjectConfig() {
|
|
48
|
+
try {
|
|
49
|
+
if (await fs.pathExists(this.configFile)) {
|
|
50
|
+
return await fs.readJson(this.configFile);
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Error loading project configuration:', error.message);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Add a module to the project configuration
|
|
61
|
+
* @param {string} moduleName - Name of the module
|
|
62
|
+
* @param {Object} options - Module options
|
|
63
|
+
*/
|
|
64
|
+
async addModule(moduleName, options = {}) {
|
|
65
|
+
const config = await this.loadProjectConfig();
|
|
66
|
+
if (!config) {
|
|
67
|
+
throw new Error('Project configuration not found. Are you in an eva4j project?');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const module = {
|
|
71
|
+
name: moduleName,
|
|
72
|
+
createdAt: new Date().toISOString(),
|
|
73
|
+
...options
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
config.modules.push(module);
|
|
77
|
+
config.updatedAt = new Date().toISOString();
|
|
78
|
+
|
|
79
|
+
await fs.writeJson(this.configFile, config, { spaces: 2 });
|
|
80
|
+
return config;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if a module exists in the project
|
|
85
|
+
* @param {string} moduleName - Name of the module to check
|
|
86
|
+
* @returns {boolean} True if module exists
|
|
87
|
+
*/
|
|
88
|
+
async moduleExists(moduleName) {
|
|
89
|
+
const config = await this.loadProjectConfig();
|
|
90
|
+
if (!config) return false;
|
|
91
|
+
|
|
92
|
+
return config.modules.some(module => module.name === moduleName);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get all modules from the project configuration
|
|
97
|
+
* @returns {Array} List of modules
|
|
98
|
+
*/
|
|
99
|
+
async getModules() {
|
|
100
|
+
const config = await this.loadProjectConfig();
|
|
101
|
+
return config ? config.modules : [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if .eva4j.json exists in the project
|
|
106
|
+
* @returns {boolean} True if config file exists
|
|
107
|
+
*/
|
|
108
|
+
async exists() {
|
|
109
|
+
return await fs.pathExists(this.configFile);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get the full project configuration path
|
|
114
|
+
* @returns {string} Path to .eva4j.json
|
|
115
|
+
*/
|
|
116
|
+
getConfigPath() {
|
|
117
|
+
return this.configFile;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if a feature exists in the project
|
|
122
|
+
* @param {string} featureName - Name of the feature to check
|
|
123
|
+
* @returns {boolean} True if feature exists
|
|
124
|
+
*/
|
|
125
|
+
async featureExists(featureName) {
|
|
126
|
+
const config = await this.loadProjectConfig();
|
|
127
|
+
if (!config || !config.features) return false;
|
|
128
|
+
|
|
129
|
+
return config.features.includes(featureName);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Add a feature to the project configuration
|
|
134
|
+
* @param {string} featureName - Name of the feature to add
|
|
135
|
+
*/
|
|
136
|
+
async addFeature(featureName) {
|
|
137
|
+
const config = await this.loadProjectConfig();
|
|
138
|
+
if (!config) {
|
|
139
|
+
throw new Error('Project configuration not found. Are you in an eva4j project?');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!config.features) {
|
|
143
|
+
config.features = [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!config.features.includes(featureName)) {
|
|
147
|
+
config.features.push(featureName);
|
|
148
|
+
config.updatedAt = new Date().toISOString();
|
|
149
|
+
await fs.writeJson(this.configFile, config, { spaces: 2 });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return config;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = ConfigManager;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const defaults = require('../../config/defaults.json');
|
|
2
|
+
const {
|
|
3
|
+
toPackagePath,
|
|
4
|
+
getApplicationClassName,
|
|
5
|
+
getFullPackageName,
|
|
6
|
+
toPascalCase,
|
|
7
|
+
toSnakeCase,
|
|
8
|
+
pluralizeWord,
|
|
9
|
+
getBaseEntity
|
|
10
|
+
} = require('./naming');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build context for base project generation
|
|
14
|
+
*/
|
|
15
|
+
function buildBaseContext(answers) {
|
|
16
|
+
const packageName = getFullPackageName(answers.groupId, answers.artifactId);
|
|
17
|
+
const packagePath = toPackagePath(packageName);
|
|
18
|
+
const applicationClassName = getApplicationClassName(answers.artifactId);
|
|
19
|
+
|
|
20
|
+
const context = {
|
|
21
|
+
projectName: answers.projectName || answers.artifactId,
|
|
22
|
+
artifactId: answers.artifactId,
|
|
23
|
+
groupId: answers.groupId,
|
|
24
|
+
version: defaults.version,
|
|
25
|
+
packageName,
|
|
26
|
+
packagePath,
|
|
27
|
+
applicationClassName,
|
|
28
|
+
javaVersion: answers.javaVersion || defaults.javaVersion,
|
|
29
|
+
springBootVersion: answers.springBootVersion || defaults.springBootVersion,
|
|
30
|
+
dependencyManagementVersion: defaults.dependencyManagementVersion,
|
|
31
|
+
springModulithVersion: defaults.springModulithVersion,
|
|
32
|
+
springCloudVersion: defaults.springCloudVersion,
|
|
33
|
+
gradleVersion: defaults.gradleVersion,
|
|
34
|
+
dependencies: answers.dependencies || [],
|
|
35
|
+
author: answers.author || 'Generated by eva4j',
|
|
36
|
+
createdDate: new Date().toISOString().split('T')[0],
|
|
37
|
+
license: 'MIT',
|
|
38
|
+
description: answers.description || `Spring Boot application: ${answers.artifactId}`,
|
|
39
|
+
...buildDatabaseContext(answers),
|
|
40
|
+
...buildServerContext(answers),
|
|
41
|
+
features: {
|
|
42
|
+
...defaults.features,
|
|
43
|
+
includeAudit: answers.dependencies?.includes('data-jpa'),
|
|
44
|
+
...answers.features
|
|
45
|
+
},
|
|
46
|
+
testing: defaults.testing,
|
|
47
|
+
loggingLevel: defaults.logging.level
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return context;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build database configuration context
|
|
55
|
+
*/
|
|
56
|
+
function buildDatabaseContext(answers) {
|
|
57
|
+
if (!answers.dependencies?.includes('data-jpa')) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const databaseType = answers.databaseType || 'h2';
|
|
62
|
+
const databaseName = answers.artifactId.replace(/-/g, '_');
|
|
63
|
+
|
|
64
|
+
const dbConfig = {
|
|
65
|
+
h2: {
|
|
66
|
+
driver: 'com.h2.database:h2',
|
|
67
|
+
driverClass: 'org.h2.Driver',
|
|
68
|
+
url: `jdbc:h2:mem:${databaseName}`,
|
|
69
|
+
username: 'sa',
|
|
70
|
+
password: '',
|
|
71
|
+
hibernateDialect: 'org.hibernate.dialect.H2Dialect',
|
|
72
|
+
testcontainer: 'h2'
|
|
73
|
+
},
|
|
74
|
+
postgresql: {
|
|
75
|
+
driver: 'org.postgresql:postgresql',
|
|
76
|
+
driverClass: 'org.postgresql.Driver',
|
|
77
|
+
url: `jdbc:postgresql://localhost:5432/${databaseName}`,
|
|
78
|
+
username: answers.databaseUsername || 'postgres',
|
|
79
|
+
password: answers.databasePassword || 'postgres',
|
|
80
|
+
hibernateDialect: 'org.hibernate.dialect.PostgreSQLDialect',
|
|
81
|
+
testcontainer: 'postgresql'
|
|
82
|
+
},
|
|
83
|
+
mysql: {
|
|
84
|
+
driver: 'com.mysql:mysql-connector-j',
|
|
85
|
+
driverClass: 'com.mysql.cj.jdbc.Driver',
|
|
86
|
+
url: `jdbc:mysql://localhost:3306/${databaseName}`,
|
|
87
|
+
username: answers.databaseUsername || 'root',
|
|
88
|
+
password: answers.databasePassword || 'root',
|
|
89
|
+
hibernateDialect: 'org.hibernate.dialect.MySQLDialect',
|
|
90
|
+
testcontainer: 'mysql'
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const config = dbConfig[databaseType];
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
databaseType,
|
|
98
|
+
databaseName,
|
|
99
|
+
databaseDriver: config.driver,
|
|
100
|
+
databaseDriverClass: config.driverClass,
|
|
101
|
+
databaseUrl: config.url,
|
|
102
|
+
databaseUsername: config.username,
|
|
103
|
+
databasePassword: config.password,
|
|
104
|
+
hibernateDialect: config.hibernateDialect,
|
|
105
|
+
databaseTestcontainer: config.testcontainer,
|
|
106
|
+
ddlAuto: defaults.database.ddlAuto,
|
|
107
|
+
showSql: defaults.database.showSql
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Build server configuration context
|
|
113
|
+
*/
|
|
114
|
+
function buildServerContext(answers) {
|
|
115
|
+
return {
|
|
116
|
+
serverPort: answers.serverPort || defaults.server.port,
|
|
117
|
+
contextPath: answers.contextPath || defaults.server.contextPath
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Build context for module generation
|
|
123
|
+
*/
|
|
124
|
+
function buildModuleContext(baseContext, moduleName, moduleOptions = {}) {
|
|
125
|
+
const className = toPascalCase(moduleName);
|
|
126
|
+
const tableName = toSnakeCase(pluralizeWord(moduleName));
|
|
127
|
+
const moduleNamePlural = pluralizeWord(moduleName);
|
|
128
|
+
|
|
129
|
+
const hasSoftDelete = moduleOptions.hasSoftDelete ?? defaults.module.hasSoftDelete;
|
|
130
|
+
const hasAudit = moduleOptions.hasAudit ?? defaults.module.hasAudit;
|
|
131
|
+
const baseEntityClass = getBaseEntity(hasSoftDelete, hasAudit);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
...baseContext,
|
|
135
|
+
moduleName,
|
|
136
|
+
moduleNamePlural,
|
|
137
|
+
className,
|
|
138
|
+
tableName,
|
|
139
|
+
hasSoftDelete,
|
|
140
|
+
hasAudit,
|
|
141
|
+
baseEntityClass,
|
|
142
|
+
generateTests: moduleOptions.generateTests ?? defaults.module.generateTests
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
buildBaseContext,
|
|
148
|
+
buildModuleContext
|
|
149
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const pluralize = require('pluralize');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert string to PascalCase
|
|
5
|
+
* @param {string} str - Input string
|
|
6
|
+
* @returns {string} PascalCase string
|
|
7
|
+
*/
|
|
8
|
+
function toPascalCase(str) {
|
|
9
|
+
return str
|
|
10
|
+
.replace(/[-_\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''))
|
|
11
|
+
.replace(/^(.)/, (char) => char.toUpperCase());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert string to camelCase
|
|
16
|
+
* @param {string} str - Input string
|
|
17
|
+
* @returns {string} camelCase string
|
|
18
|
+
*/
|
|
19
|
+
function toCamelCase(str) {
|
|
20
|
+
const pascal = toPascalCase(str);
|
|
21
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Convert string to snake_case
|
|
26
|
+
* @param {string} str - Input string
|
|
27
|
+
* @returns {string} snake_case string
|
|
28
|
+
*/
|
|
29
|
+
function toSnakeCase(str) {
|
|
30
|
+
return str
|
|
31
|
+
.replace(/([A-Z])/g, '_$1')
|
|
32
|
+
.replace(/[-\s]+/g, '_')
|
|
33
|
+
.replace(/^_/, '')
|
|
34
|
+
.toLowerCase();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Convert string to kebab-case
|
|
39
|
+
* @param {string} str - Input string
|
|
40
|
+
* @returns {string} kebab-case string
|
|
41
|
+
*/
|
|
42
|
+
function toKebabCase(str) {
|
|
43
|
+
return str
|
|
44
|
+
.replace(/([A-Z])/g, '-$1')
|
|
45
|
+
.replace(/[\s_]+/g, '-')
|
|
46
|
+
.replace(/^-/, '')
|
|
47
|
+
.toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Pluralize a word
|
|
52
|
+
* @param {string} word - Word to pluralize
|
|
53
|
+
* @returns {string} Pluralized word
|
|
54
|
+
*/
|
|
55
|
+
function pluralizeWord(word) {
|
|
56
|
+
return pluralize(word);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Convert package name to path
|
|
61
|
+
* @param {string} packageName - Package name (e.g., com.company.project)
|
|
62
|
+
* @returns {string} Package path (e.g., com/company/project)
|
|
63
|
+
*/
|
|
64
|
+
function toPackagePath(packageName) {
|
|
65
|
+
return packageName.replace(/\./g, '/');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get base entity class based on flags
|
|
70
|
+
* @param {boolean} hasSoftDelete - Whether to use soft delete
|
|
71
|
+
* @param {boolean} hasAudit - Whether to use audit fields
|
|
72
|
+
* @returns {string} Base entity class name
|
|
73
|
+
*/
|
|
74
|
+
function getBaseEntity(hasSoftDelete, hasAudit) {
|
|
75
|
+
if (hasSoftDelete) {
|
|
76
|
+
return 'SoftDeletableEntity';
|
|
77
|
+
}
|
|
78
|
+
if (hasAudit) {
|
|
79
|
+
return 'AuditableEntity';
|
|
80
|
+
}
|
|
81
|
+
return 'BaseEntity';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Convert artifact ID to valid Java package name
|
|
86
|
+
* @param {string} artifactId - Artifact ID (e.g., my-project)
|
|
87
|
+
* @returns {string} Valid package name (e.g., myproject)
|
|
88
|
+
*/
|
|
89
|
+
function artifactIdToPackageName(artifactId) {
|
|
90
|
+
return artifactId
|
|
91
|
+
.toLowerCase()
|
|
92
|
+
.replace(/[^a-z0-9]/g, '');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Generate application class name from artifact ID
|
|
97
|
+
* @param {string} artifactId - Artifact ID (e.g., my-project)
|
|
98
|
+
* @returns {string} Application class name (e.g., MyProjectApplication)
|
|
99
|
+
*/
|
|
100
|
+
function getApplicationClassName(artifactId) {
|
|
101
|
+
return toPascalCase(artifactId) + 'Application';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get full package name
|
|
106
|
+
* @param {string} groupId - Group ID (e.g., com.company)
|
|
107
|
+
* @param {string} artifactId - Artifact ID (e.g., my-project)
|
|
108
|
+
* @returns {string} Full package name (e.g., com.company.myproject)
|
|
109
|
+
*/
|
|
110
|
+
function getFullPackageName(groupId, artifactId) {
|
|
111
|
+
const packagePart = artifactIdToPackageName(artifactId);
|
|
112
|
+
return `${groupId}.${packagePart}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Check if a use case name represents an "all" type query that returns a list
|
|
117
|
+
* @param {string} usecaseName - Use case name (e.g., FindAllUsers, GetAllProducts)
|
|
118
|
+
* @returns {boolean} True if the use case returns a list
|
|
119
|
+
*/
|
|
120
|
+
function isAllTypeQuery(usecaseName) {
|
|
121
|
+
const allPatterns = ['FindAll', 'GetAll', 'ListAll', 'SearchAll', 'RetrieveAll'];
|
|
122
|
+
return allPatterns.some(pattern => usecaseName.startsWith(pattern));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
toPascalCase,
|
|
127
|
+
toCamelCase,
|
|
128
|
+
toSnakeCase,
|
|
129
|
+
toKebabCase,
|
|
130
|
+
pluralizeWord,
|
|
131
|
+
toPackagePath,
|
|
132
|
+
getBaseEntity,
|
|
133
|
+
artifactIdToPackageName,
|
|
134
|
+
getApplicationClassName,
|
|
135
|
+
getFullPackageName,
|
|
136
|
+
isAllTypeQuery
|
|
137
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const ejs = require('ejs');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Render a template file with given context
|
|
7
|
+
* @param {string} templatePath - Path to template file
|
|
8
|
+
* @param {object} context - Template variables
|
|
9
|
+
* @returns {Promise<string>} Rendered template
|
|
10
|
+
*/
|
|
11
|
+
async function renderTemplate(templatePath, context) {
|
|
12
|
+
const templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
13
|
+
return ejs.render(templateContent, context);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Render and write template to destination
|
|
18
|
+
* @param {string} templatePath - Path to template file
|
|
19
|
+
* @param {string} destPath - Destination file path
|
|
20
|
+
* @param {object} context - Template variables
|
|
21
|
+
*/
|
|
22
|
+
async function renderAndWrite(templatePath, destPath, context) {
|
|
23
|
+
const content = await renderTemplate(templatePath, context);
|
|
24
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
25
|
+
await fs.writeFile(destPath, content, 'utf-8');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get template file paths recursively
|
|
30
|
+
* @param {string} dir - Directory to scan
|
|
31
|
+
* @param {Array<string>} fileList - Accumulated file list
|
|
32
|
+
* @returns {Promise<Array<string>>} List of template file paths
|
|
33
|
+
*/
|
|
34
|
+
async function getTemplateFiles(dir, fileList = []) {
|
|
35
|
+
const files = await fs.readdir(dir);
|
|
36
|
+
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
const filePath = path.join(dir, file);
|
|
39
|
+
const stat = await fs.stat(filePath);
|
|
40
|
+
|
|
41
|
+
if (stat.isDirectory()) {
|
|
42
|
+
await getTemplateFiles(filePath, fileList);
|
|
43
|
+
} else if (file.endsWith('.ejs')) {
|
|
44
|
+
fileList.push(filePath);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return fileList;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
renderTemplate,
|
|
53
|
+
renderAndWrite,
|
|
54
|
+
getTemplateFiles
|
|
55
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Validate project name
|
|
6
|
+
* @param {string} name - Project name
|
|
7
|
+
* @returns {boolean|string} True if valid, error message otherwise
|
|
8
|
+
*/
|
|
9
|
+
function validateProjectName(name) {
|
|
10
|
+
if (!name || name.trim() === '') {
|
|
11
|
+
return 'Project name cannot be empty';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
15
|
+
return 'Project name must contain only lowercase letters, numbers, and hyphens';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (name.startsWith('-') || name.endsWith('-')) {
|
|
19
|
+
return 'Project name cannot start or end with a hyphen';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (name.length < 2) {
|
|
23
|
+
return 'Project name must be at least 2 characters long';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validate group ID (Java package naming convention)
|
|
31
|
+
* @param {string} groupId - Group ID
|
|
32
|
+
* @returns {boolean|string} True if valid, error message otherwise
|
|
33
|
+
*/
|
|
34
|
+
function validateGroupId(groupId) {
|
|
35
|
+
if (!groupId || groupId.trim() === '') {
|
|
36
|
+
return 'Group ID cannot be empty';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const parts = groupId.split('.');
|
|
40
|
+
|
|
41
|
+
if (parts.length < 2) {
|
|
42
|
+
return 'Group ID must have at least 2 parts (e.g., com.company)';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const part of parts) {
|
|
46
|
+
if (!/^[a-z][a-z0-9]*$/.test(part)) {
|
|
47
|
+
return 'Each part of Group ID must start with a lowercase letter and contain only lowercase letters and numbers';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validate module name
|
|
56
|
+
* @param {string} name - Module name
|
|
57
|
+
* @returns {boolean|string} True if valid, error message otherwise
|
|
58
|
+
*/
|
|
59
|
+
function validateModuleName(name) {
|
|
60
|
+
if (!name || name.trim() === '') {
|
|
61
|
+
return 'Module name cannot be empty';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
65
|
+
return 'Module name must start with a lowercase letter and contain only lowercase letters and numbers';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (name.length < 2) {
|
|
69
|
+
return 'Module name must be at least 2 characters long';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Reserved names
|
|
73
|
+
const reserved = ['common', 'shared', 'config', 'util', 'test'];
|
|
74
|
+
if (reserved.includes(name)) {
|
|
75
|
+
return `Module name '${name}' is reserved and cannot be used`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if current directory is a valid eva4j project
|
|
83
|
+
* @param {string} dir - Directory path
|
|
84
|
+
* @returns {boolean} True if valid project
|
|
85
|
+
*/
|
|
86
|
+
async function isEva4jProject(dir) {
|
|
87
|
+
const buildGradlePath = path.join(dir, 'build.gradle');
|
|
88
|
+
|
|
89
|
+
if (!await fs.pathExists(buildGradlePath)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check if build.gradle contains Spring Boot plugin
|
|
94
|
+
const content = await fs.readFile(buildGradlePath, 'utf-8');
|
|
95
|
+
return content.includes('org.springframework.boot');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check if module already exists
|
|
100
|
+
* @param {string} projectDir - Project directory
|
|
101
|
+
* @param {string} packagePath - Package path
|
|
102
|
+
* @param {string} moduleName - Module name
|
|
103
|
+
* @returns {boolean} True if module exists
|
|
104
|
+
*/
|
|
105
|
+
async function moduleExists(projectDir, packagePath, moduleName) {
|
|
106
|
+
const modulePath = path.join(projectDir, 'src', 'main', 'java', packagePath, moduleName);
|
|
107
|
+
return await fs.pathExists(modulePath);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if shared module exists
|
|
112
|
+
* @param {string} projectDir - Project directory
|
|
113
|
+
* @param {string} packagePath - Package path
|
|
114
|
+
* @returns {boolean} True if shared module exists
|
|
115
|
+
*/
|
|
116
|
+
async function sharedModuleExists(projectDir, packagePath) {
|
|
117
|
+
const sharedPath = path.join(projectDir, 'src', 'main', 'java', packagePath, 'shared');
|
|
118
|
+
return await fs.pathExists(sharedPath);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validate Java version
|
|
123
|
+
* @param {number} version - Java version
|
|
124
|
+
* @returns {boolean} True if valid
|
|
125
|
+
*/
|
|
126
|
+
function validateJavaVersion(version) {
|
|
127
|
+
const validVersions = [21, 22, 23];
|
|
128
|
+
return validVersions.includes(version);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Validate port number
|
|
133
|
+
* @param {number} port - Port number
|
|
134
|
+
* @returns {boolean|string} True if valid, error message otherwise
|
|
135
|
+
*/
|
|
136
|
+
function validatePort(port) {
|
|
137
|
+
const portNum = parseInt(port, 10);
|
|
138
|
+
|
|
139
|
+
if (isNaN(portNum)) {
|
|
140
|
+
return 'Port must be a number';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (portNum < 1024 || portNum > 65535) {
|
|
144
|
+
return 'Port must be between 1024 and 65535';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
validateProjectName,
|
|
152
|
+
validateGroupId,
|
|
153
|
+
validateModuleName,
|
|
154
|
+
isEva4jProject,
|
|
155
|
+
moduleExists,
|
|
156
|
+
sharedModuleExists,
|
|
157
|
+
validateJavaVersion,
|
|
158
|
+
validatePort
|
|
159
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package <%= packageName %>;
|
|
2
|
+
|
|
3
|
+
import org.springframework.boot.SpringApplication;
|
|
4
|
+
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
5
|
+
import org.springframework.cloud.openfeign.EnableFeignClients;
|
|
6
|
+
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
|
7
|
+
<% if (features.enableScheduling) { %>import org.springframework.scheduling.annotation.EnableScheduling;
|
|
8
|
+
<% } %><% if (features.enableAsync) { %>import org.springframework.scheduling.annotation.EnableAsync;
|
|
9
|
+
<% } %>
|
|
10
|
+
/**
|
|
11
|
+
* Main application class for <%= projectName %>
|
|
12
|
+
*
|
|
13
|
+
* @author <%= author %>
|
|
14
|
+
* @version <%= version %>
|
|
15
|
+
* @since <%= createdDate %>
|
|
16
|
+
*/
|
|
17
|
+
@SpringBootApplication
|
|
18
|
+
@EnableFeignClients
|
|
19
|
+
@EnableJpaAuditing
|
|
20
|
+
<% if (features.enableScheduling) { %>@EnableScheduling
|
|
21
|
+
<% } %><% if (features.enableAsync) { %>@EnableAsync
|
|
22
|
+
<% } %>public class <%= applicationClassName %> {
|
|
23
|
+
|
|
24
|
+
public static void main(String[] args) {
|
|
25
|
+
SpringApplication.run(<%= applicationClassName %>.class, args);
|
|
26
|
+
}
|
|
27
|
+
}
|