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,495 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const BaseGenerator = require('../generators/base-generator');
|
|
7
|
+
const { buildBaseContext } = require('../utils/context-builder');
|
|
8
|
+
const { validateModuleName, isEva4jProject, moduleExists } = require('../utils/validator');
|
|
9
|
+
const { toPackagePath, toPascalCase, getApplicationClassName } = require('../utils/naming');
|
|
10
|
+
const ConfigManager = require('../utils/config-manager');
|
|
11
|
+
const defaults = require('../../config/defaults.json');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detach a module from the monolith and create a standalone microservice
|
|
15
|
+
*/
|
|
16
|
+
async function detachCommand(moduleName, options) {
|
|
17
|
+
const projectDir = process.cwd();
|
|
18
|
+
|
|
19
|
+
// Validate we're in an eva4j project
|
|
20
|
+
if (!(await isEva4jProject(projectDir))) {
|
|
21
|
+
console.error(chalk.red('❌ Not in an eva4j project directory'));
|
|
22
|
+
console.error(chalk.gray('Run this command inside a project created with eva4j'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Load project configuration
|
|
27
|
+
const configManager = new ConfigManager(projectDir);
|
|
28
|
+
const projectConfig = await configManager.loadProjectConfig();
|
|
29
|
+
|
|
30
|
+
if (!projectConfig) {
|
|
31
|
+
console.error(chalk.red('❌ Could not load project configuration'));
|
|
32
|
+
console.error(chalk.gray('Make sure .eva4j.json exists in the project root'));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { packageName, packagePath: parentPackagePath } = projectConfig;
|
|
37
|
+
const packagePath = toPackagePath(packageName);
|
|
38
|
+
|
|
39
|
+
// Prompt for module name if not provided
|
|
40
|
+
if (!moduleName) {
|
|
41
|
+
const nameAnswer = await inquirer.prompt([
|
|
42
|
+
{
|
|
43
|
+
type: 'input',
|
|
44
|
+
name: 'moduleName',
|
|
45
|
+
message: 'Enter module name to detach:',
|
|
46
|
+
validate: (input) => {
|
|
47
|
+
const validation = validateModuleName(input);
|
|
48
|
+
if (validation !== true) {
|
|
49
|
+
return validation;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
]);
|
|
55
|
+
moduleName = nameAnswer.moduleName;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Validate module exists
|
|
59
|
+
const moduleDir = path.join(projectDir, 'src', 'main', 'java', packagePath, moduleName);
|
|
60
|
+
if (!(await fs.pathExists(moduleDir))) {
|
|
61
|
+
console.error(chalk.red(`❌ Module "${moduleName}" not found`));
|
|
62
|
+
console.error(chalk.gray(`Expected location: src/main/java/${packagePath}/${moduleName}`));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if module is registered in config
|
|
67
|
+
const moduleConfig = projectConfig.modules?.find(m => m.name === moduleName);
|
|
68
|
+
if (!moduleConfig) {
|
|
69
|
+
console.error(chalk.yellow(`⚠️ Module "${moduleName}" not found in .eva4j.json`));
|
|
70
|
+
const continueAnswer = await inquirer.prompt([
|
|
71
|
+
{
|
|
72
|
+
type: 'confirm',
|
|
73
|
+
name: 'continue',
|
|
74
|
+
message: 'Continue anyway?',
|
|
75
|
+
default: false
|
|
76
|
+
}
|
|
77
|
+
]);
|
|
78
|
+
if (!continueAnswer.continue) {
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Determine new project name and path
|
|
84
|
+
const newProjectName = `${moduleName}_msvc`;
|
|
85
|
+
const parentDir = path.dirname(projectDir);
|
|
86
|
+
const newProjectDir = path.join(parentDir, newProjectName);
|
|
87
|
+
|
|
88
|
+
// Check if destination already exists
|
|
89
|
+
if (await fs.pathExists(newProjectDir)) {
|
|
90
|
+
console.error(chalk.red(`❌ Destination directory already exists: ${newProjectName}`));
|
|
91
|
+
console.error(chalk.gray('Please remove or rename the existing directory'));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Get current server port and increment
|
|
96
|
+
const applicationYml = await fs.readFile(
|
|
97
|
+
path.join(projectDir, 'src', 'main', 'resources', 'application.yml'),
|
|
98
|
+
'utf-8'
|
|
99
|
+
);
|
|
100
|
+
const portMatch = applicationYml.match(/port:\s*(\d+)/);
|
|
101
|
+
const parentPort = portMatch ? parseInt(portMatch[1]) : 8040;
|
|
102
|
+
const newPort = parentPort + 1;
|
|
103
|
+
|
|
104
|
+
// Check if shared module exists (needed for copying)
|
|
105
|
+
const sharedDir = path.join(projectDir, 'src', 'main', 'java', packagePath, 'shared');
|
|
106
|
+
if (!(await fs.pathExists(sharedDir))) {
|
|
107
|
+
console.error(chalk.red('❌ Shared module not found'));
|
|
108
|
+
console.error(chalk.gray('The project must have a shared module to detach a module'));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Show confirmation prompt
|
|
113
|
+
console.log(chalk.blue('\n📦 Module Detachment Summary:'));
|
|
114
|
+
console.log(chalk.gray('─────────────────────────────────────'));
|
|
115
|
+
console.log(chalk.white(` Module: ${moduleName}`));
|
|
116
|
+
console.log(chalk.white(` New Project: ${newProjectName}`));
|
|
117
|
+
console.log(chalk.white(` Location: ${newProjectDir}`));
|
|
118
|
+
console.log(chalk.white(` Package: ${packageName}.${moduleName}`));
|
|
119
|
+
console.log(chalk.white(` Parent Port: ${parentPort}`));
|
|
120
|
+
console.log(chalk.white(` New Port: ${newPort}`));
|
|
121
|
+
console.log(chalk.white(` Database: ${projectConfig.groupId || 'Same as parent'}`));
|
|
122
|
+
console.log(chalk.gray('─────────────────────────────────────'));
|
|
123
|
+
|
|
124
|
+
const confirmAnswer = await inquirer.prompt([
|
|
125
|
+
{
|
|
126
|
+
type: 'confirm',
|
|
127
|
+
name: 'confirm',
|
|
128
|
+
message: 'Proceed with module detachment?',
|
|
129
|
+
default: false
|
|
130
|
+
}
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
if (!confirmAnswer.confirm) {
|
|
134
|
+
console.log(chalk.gray('Detachment cancelled'));
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const spinner = ora('Detaching module...').start();
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
// Step 1: Create new project directory
|
|
142
|
+
await fs.ensureDir(newProjectDir);
|
|
143
|
+
|
|
144
|
+
// Step 2: Build context for new project
|
|
145
|
+
// Use database type from parent project config
|
|
146
|
+
const databaseType = projectConfig.databaseType || 'h2';
|
|
147
|
+
const databaseName = newProjectName.replace(/-/g, '_');
|
|
148
|
+
|
|
149
|
+
const dbConfig = {
|
|
150
|
+
h2: {
|
|
151
|
+
driver: 'com.h2.database:h2',
|
|
152
|
+
driverClass: 'org.h2.Driver',
|
|
153
|
+
url: `jdbc:h2:mem:${databaseName}`,
|
|
154
|
+
username: 'sa',
|
|
155
|
+
password: '',
|
|
156
|
+
hibernateDialect: 'org.hibernate.dialect.H2Dialect',
|
|
157
|
+
testcontainer: 'h2'
|
|
158
|
+
},
|
|
159
|
+
postgresql: {
|
|
160
|
+
driver: 'org.postgresql:postgresql',
|
|
161
|
+
driverClass: 'org.postgresql.Driver',
|
|
162
|
+
url: `jdbc:postgresql://localhost:5432/${databaseName}`,
|
|
163
|
+
username: 'postgres',
|
|
164
|
+
password: 'postgres',
|
|
165
|
+
hibernateDialect: 'org.hibernate.dialect.PostgreSQLDialect',
|
|
166
|
+
testcontainer: 'postgresql'
|
|
167
|
+
},
|
|
168
|
+
mysql: {
|
|
169
|
+
driver: 'com.mysql:mysql-connector-j',
|
|
170
|
+
driverClass: 'com.mysql.cj.jdbc.Driver',
|
|
171
|
+
url: `jdbc:mysql://localhost:3306/${databaseName}`,
|
|
172
|
+
username: 'root',
|
|
173
|
+
password: 'root',
|
|
174
|
+
hibernateDialect: 'org.hibernate.dialect.MySQLDialect',
|
|
175
|
+
testcontainer: 'mysql'
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const dbSettings = dbConfig[databaseType];
|
|
180
|
+
|
|
181
|
+
const detachedContext = {
|
|
182
|
+
...projectConfig,
|
|
183
|
+
projectName: newProjectName,
|
|
184
|
+
artifactId: moduleName,
|
|
185
|
+
packagePath: toPackagePath(packageName),
|
|
186
|
+
moduleName: moduleName,
|
|
187
|
+
serverPort: newPort,
|
|
188
|
+
applicationClassName: getApplicationClassName(newProjectName),
|
|
189
|
+
version: '1.0.0',
|
|
190
|
+
author: projectConfig.author || 'Eva4j',
|
|
191
|
+
createdDate: new Date().toISOString().split('T')[0],
|
|
192
|
+
dependencyManagementVersion: defaults.dependencyManagementVersion,
|
|
193
|
+
springCloudVersion: projectConfig.springCloudVersion || defaults.springCloudVersion,
|
|
194
|
+
gradleVersion: defaults.gradleVersion,
|
|
195
|
+
license: 'MIT',
|
|
196
|
+
description: `Detached microservice: ${newProjectName}`,
|
|
197
|
+
contextPath: '/',
|
|
198
|
+
isDetached: true,
|
|
199
|
+
// Database configuration
|
|
200
|
+
databaseType,
|
|
201
|
+
databaseName,
|
|
202
|
+
databaseDriver: dbSettings.driver,
|
|
203
|
+
databaseDriverClass: dbSettings.driverClass,
|
|
204
|
+
databaseUrl: dbSettings.url,
|
|
205
|
+
databaseUsername: dbSettings.username,
|
|
206
|
+
databasePassword: dbSettings.password,
|
|
207
|
+
hibernateDialect: dbSettings.hibernateDialect,
|
|
208
|
+
databaseTestcontainer: dbSettings.testcontainer,
|
|
209
|
+
ddlAuto: 'update',
|
|
210
|
+
showSql: true,
|
|
211
|
+
loggingLevel: 'INFO',
|
|
212
|
+
features: {
|
|
213
|
+
enableScheduling: false,
|
|
214
|
+
enableAsync: false,
|
|
215
|
+
includeSwagger: true,
|
|
216
|
+
includeDocker: true,
|
|
217
|
+
includeLombok: true,
|
|
218
|
+
includeDevtools: true,
|
|
219
|
+
includeActuator: true,
|
|
220
|
+
includeAudit: projectConfig.dependencies?.includes('data-jpa'),
|
|
221
|
+
hasKafka: projectConfig.features?.includes('kafka') || false
|
|
222
|
+
},
|
|
223
|
+
testing: defaults.testing
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
spinner.text = 'Generating base project structure...';
|
|
227
|
+
|
|
228
|
+
// Step 3: Generate base project using BaseGenerator
|
|
229
|
+
await generateDetachedProject(newProjectDir, detachedContext);
|
|
230
|
+
|
|
231
|
+
spinner.text = 'Copying module files...';
|
|
232
|
+
|
|
233
|
+
// Step 4: Copy module directory
|
|
234
|
+
const newModuleDir = path.join(newProjectDir, 'src', 'main', 'java', packagePath, moduleName);
|
|
235
|
+
await fs.copy(moduleDir, newModuleDir);
|
|
236
|
+
|
|
237
|
+
spinner.text = 'Merging shared components...';
|
|
238
|
+
|
|
239
|
+
// Step 5: Merge shared/domain into module/domain
|
|
240
|
+
await mergeSharedComponents(
|
|
241
|
+
sharedDir,
|
|
242
|
+
newModuleDir,
|
|
243
|
+
packageName,
|
|
244
|
+
moduleName
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
spinner.text = 'Updating package references...';
|
|
248
|
+
|
|
249
|
+
// Step 6: Update all imports in module files
|
|
250
|
+
await updatePackageReferences(
|
|
251
|
+
newModuleDir,
|
|
252
|
+
packageName,
|
|
253
|
+
moduleName
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
spinner.text = 'Cleaning up...';
|
|
257
|
+
|
|
258
|
+
// Step 7: Remove package-info.java files
|
|
259
|
+
await removePackageInfoFiles(newModuleDir);
|
|
260
|
+
|
|
261
|
+
// Step 8: Copy test files if they exist
|
|
262
|
+
const testModuleDir = path.join(projectDir, 'src', 'test', 'java', packagePath, moduleName);
|
|
263
|
+
if (await fs.pathExists(testModuleDir)) {
|
|
264
|
+
const newTestModuleDir = path.join(newProjectDir, 'src', 'test', 'java', packagePath, moduleName);
|
|
265
|
+
await fs.copy(testModuleDir, newTestModuleDir);
|
|
266
|
+
await updatePackageReferences(newTestModuleDir, packageName, moduleName);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Step 9: Create detached project configuration
|
|
270
|
+
const detachedConfigManager = new ConfigManager(newProjectDir);
|
|
271
|
+
await detachedConfigManager.saveProjectConfig({
|
|
272
|
+
...detachedContext,
|
|
273
|
+
modules: [{
|
|
274
|
+
name: moduleName,
|
|
275
|
+
hasSoftDelete: moduleConfig?.hasSoftDelete ?? true,
|
|
276
|
+
hasAudit: moduleConfig?.hasAudit ?? true,
|
|
277
|
+
createdAt: new Date().toISOString()
|
|
278
|
+
}]
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
spinner.text = 'Copying environment configurations...';
|
|
282
|
+
|
|
283
|
+
// Step 10: Copy environment profile files from parent resources
|
|
284
|
+
await copyEnvironmentProfiles(projectDir, newProjectDir, packageName, moduleName);
|
|
285
|
+
|
|
286
|
+
spinner.succeed(chalk.green('✅ Module detached successfully! ✨'));
|
|
287
|
+
|
|
288
|
+
// Display success message
|
|
289
|
+
console.log(chalk.blue('\n📦 Detached Microservice Created:'));
|
|
290
|
+
console.log(chalk.gray('─────────────────────────────────────'));
|
|
291
|
+
console.log(chalk.white(` Project: ${newProjectName}`));
|
|
292
|
+
console.log(chalk.white(` Location: ${newProjectDir}`));
|
|
293
|
+
console.log(chalk.white(` Port: ${newPort}`));
|
|
294
|
+
console.log(chalk.gray('─────────────────────────────────────'));
|
|
295
|
+
console.log(chalk.blue('\n🚀 Next Steps:'));
|
|
296
|
+
console.log(chalk.white(` 1. cd ${newProjectName}`));
|
|
297
|
+
console.log(chalk.white(` 2. ./gradlew build`));
|
|
298
|
+
console.log(chalk.white(` 3. ./gradlew bootRun`));
|
|
299
|
+
console.log(chalk.gray('\n The microservice will run on port ' + newPort));
|
|
300
|
+
|
|
301
|
+
} catch (error) {
|
|
302
|
+
spinner.fail(chalk.red('Failed to detach module'));
|
|
303
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
304
|
+
if (error.stack) {
|
|
305
|
+
console.error(chalk.gray(error.stack));
|
|
306
|
+
}
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Generate base project structure for detached microservice
|
|
313
|
+
*/
|
|
314
|
+
async function generateDetachedProject(projectDir, context) {
|
|
315
|
+
const generator = new BaseGenerator(context);
|
|
316
|
+
generator.projectDir = projectDir;
|
|
317
|
+
await generator.generate();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Merge shared components into module structure
|
|
322
|
+
*/
|
|
323
|
+
async function mergeSharedComponents(sharedDir, moduleDir, packageName, moduleName) {
|
|
324
|
+
// Merge shared/domain/* into module/domain/
|
|
325
|
+
const sharedDomainDir = path.join(sharedDir, 'domain');
|
|
326
|
+
if (await fs.pathExists(sharedDomainDir)) {
|
|
327
|
+
const domainSubDirs = await fs.readdir(sharedDomainDir);
|
|
328
|
+
for (const subDir of domainSubDirs) {
|
|
329
|
+
const sourcePath = path.join(sharedDomainDir, subDir);
|
|
330
|
+
const destPath = path.join(moduleDir, 'domain', subDir);
|
|
331
|
+
|
|
332
|
+
if ((await fs.stat(sourcePath)).isDirectory()) {
|
|
333
|
+
await fs.copy(sourcePath, destPath, { overwrite: false });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Merge shared/infrastructure/* into module/infrastructure/
|
|
339
|
+
const sharedInfraDir = path.join(sharedDir, 'infrastructure');
|
|
340
|
+
if (await fs.pathExists(sharedInfraDir)) {
|
|
341
|
+
const infraSubDirs = await fs.readdir(sharedInfraDir);
|
|
342
|
+
for (const subDir of infraSubDirs) {
|
|
343
|
+
const sourcePath = path.join(sharedInfraDir, subDir);
|
|
344
|
+
const destPath = path.join(moduleDir, 'infrastructure', subDir);
|
|
345
|
+
|
|
346
|
+
if ((await fs.stat(sourcePath)).isDirectory()) {
|
|
347
|
+
await fs.copy(sourcePath, destPath, { overwrite: false });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Update package references in all Java files
|
|
355
|
+
*/
|
|
356
|
+
async function updatePackageReferences(directory, packageName, moduleName) {
|
|
357
|
+
const javaFiles = await findJavaFiles(directory);
|
|
358
|
+
|
|
359
|
+
for (const file of javaFiles) {
|
|
360
|
+
let content = await fs.readFile(file, 'utf-8');
|
|
361
|
+
let modified = false;
|
|
362
|
+
|
|
363
|
+
// Replace shared.domain.* imports with moduleName.domain.*
|
|
364
|
+
const domainPattern = new RegExp(`${packageName}\\.shared\\.domain\\.`, 'g');
|
|
365
|
+
if (domainPattern.test(content)) {
|
|
366
|
+
content = content.replace(domainPattern, `${packageName}.${moduleName}.domain.`);
|
|
367
|
+
modified = true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Replace shared.infrastructure.* imports with moduleName.infrastructure.*
|
|
371
|
+
const infraPattern = new RegExp(`${packageName}\\.shared\\.infrastructure\\.`, 'g');
|
|
372
|
+
if (infraPattern.test(content)) {
|
|
373
|
+
content = content.replace(infraPattern, `${packageName}.${moduleName}.infrastructure.`);
|
|
374
|
+
modified = true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (modified) {
|
|
378
|
+
await fs.writeFile(file, content, 'utf-8');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Remove all package-info.java files
|
|
385
|
+
*/
|
|
386
|
+
async function removePackageInfoFiles(directory) {
|
|
387
|
+
const packageInfoFiles = await findPackageInfoFiles(directory);
|
|
388
|
+
|
|
389
|
+
for (const file of packageInfoFiles) {
|
|
390
|
+
await fs.remove(file);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Find all Java files recursively
|
|
396
|
+
*/
|
|
397
|
+
async function findJavaFiles(dir) {
|
|
398
|
+
const files = [];
|
|
399
|
+
|
|
400
|
+
async function walk(currentDir) {
|
|
401
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
402
|
+
|
|
403
|
+
for (const entry of entries) {
|
|
404
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
405
|
+
|
|
406
|
+
if (entry.isDirectory()) {
|
|
407
|
+
await walk(fullPath);
|
|
408
|
+
} else if (entry.isFile() && entry.name.endsWith('.java')) {
|
|
409
|
+
files.push(fullPath);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
await walk(dir);
|
|
415
|
+
return files;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Find all package-info.java files recursively
|
|
420
|
+
*/
|
|
421
|
+
async function findPackageInfoFiles(dir) {
|
|
422
|
+
const files = [];
|
|
423
|
+
|
|
424
|
+
async function walk(currentDir) {
|
|
425
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
426
|
+
|
|
427
|
+
for (const entry of entries) {
|
|
428
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
429
|
+
|
|
430
|
+
if (entry.isDirectory()) {
|
|
431
|
+
await walk(fullPath);
|
|
432
|
+
} else if (entry.isFile() && entry.name === 'package-info.java') {
|
|
433
|
+
files.push(fullPath);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
await walk(dir);
|
|
439
|
+
return files;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Copy environment profile files from parent to detached project
|
|
444
|
+
*/
|
|
445
|
+
async function copyEnvironmentProfiles(parentDir, newProjectDir, packageName, moduleName) {
|
|
446
|
+
const parentResourcesDir = path.join(parentDir, 'src', 'main', 'resources');
|
|
447
|
+
const newResourcesDir = path.join(newProjectDir, 'src', 'main', 'resources');
|
|
448
|
+
|
|
449
|
+
// Copy environment profile files
|
|
450
|
+
const profileFiles = [
|
|
451
|
+
'application-develop.yml',
|
|
452
|
+
'application-local.yml',
|
|
453
|
+
'application-production.yml',
|
|
454
|
+
'application-test.yml'
|
|
455
|
+
];
|
|
456
|
+
|
|
457
|
+
for (const file of profileFiles) {
|
|
458
|
+
const sourcePath = path.join(parentResourcesDir, file);
|
|
459
|
+
if (await fs.pathExists(sourcePath)) {
|
|
460
|
+
await fs.copy(sourcePath, path.join(newResourcesDir, file));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Copy parameters folder if it exists
|
|
465
|
+
const parametersDir = path.join(parentResourcesDir, 'parameters');
|
|
466
|
+
if (await fs.pathExists(parametersDir)) {
|
|
467
|
+
await fs.copy(parametersDir, path.join(newResourcesDir, 'parameters'));
|
|
468
|
+
|
|
469
|
+
// Update package references in kafka.yml files
|
|
470
|
+
await updateKafkaConfigReferences(newResourcesDir, packageName, moduleName);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Update package references in kafka.yml files
|
|
476
|
+
*/
|
|
477
|
+
async function updateKafkaConfigReferences(resourcesDir, packageName, moduleName) {
|
|
478
|
+
const environments = ['local', 'develop', 'test', 'production'];
|
|
479
|
+
|
|
480
|
+
for (const env of environments) {
|
|
481
|
+
const kafkaYmlPath = path.join(resourcesDir, 'parameters', env, 'kafka.yml');
|
|
482
|
+
|
|
483
|
+
if (await fs.pathExists(kafkaYmlPath)) {
|
|
484
|
+
let content = await fs.readFile(kafkaYmlPath, 'utf-8');
|
|
485
|
+
|
|
486
|
+
// Replace .shared.infrastructure. with .{moduleName}.infrastructure.
|
|
487
|
+
const pattern = new RegExp(`${packageName}\\.shared\\.infrastructure\\.`, 'g');
|
|
488
|
+
content = content.replace(pattern, `${packageName}.${moduleName}.infrastructure.`);
|
|
489
|
+
|
|
490
|
+
await fs.writeFile(kafkaYmlPath, content, 'utf-8');
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
module.exports = detachCommand;
|