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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/QUICK_REFERENCE.md +204 -0
  3. package/README.md +912 -0
  4. package/USAGE.md +349 -0
  5. package/bin/eva4j.js +234 -0
  6. package/config/defaults.json +46 -0
  7. package/package.json +57 -0
  8. package/src/commands/add-kafka-client.js +193 -0
  9. package/src/commands/add-module.js +221 -0
  10. package/src/commands/create.js +92 -0
  11. package/src/commands/detach.js +495 -0
  12. package/src/commands/generate-http-exchange.js +309 -0
  13. package/src/commands/generate-kafka-event.js +453 -0
  14. package/src/commands/generate-kafka-listener.js +267 -0
  15. package/src/commands/generate-resource.js +265 -0
  16. package/src/commands/generate-usecase.js +198 -0
  17. package/src/commands/info.js +63 -0
  18. package/src/generators/base-generator.js +150 -0
  19. package/src/generators/module-generator.js +48 -0
  20. package/src/generators/shared-generator.js +153 -0
  21. package/src/utils/config-manager.js +156 -0
  22. package/src/utils/context-builder.js +149 -0
  23. package/src/utils/naming.js +137 -0
  24. package/src/utils/template-engine.js +55 -0
  25. package/src/utils/validator.js +159 -0
  26. package/templates/base/application/Application.java.ejs +27 -0
  27. package/templates/base/docker/docker-compose.yml.ejs +41 -0
  28. package/templates/base/gradle/build.gradle.ejs +70 -0
  29. package/templates/base/gradle/settings.gradle.ejs +1 -0
  30. package/templates/base/resources/application-develop.yml.ejs +5 -0
  31. package/templates/base/resources/application-local.yml.ejs +5 -0
  32. package/templates/base/resources/application-production.yml.ejs +9 -0
  33. package/templates/base/resources/application-test.yml.ejs +5 -0
  34. package/templates/base/resources/application.yml.ejs +31 -0
  35. package/templates/base/resources/parameters/develop/cors.yml.ejs +4 -0
  36. package/templates/base/resources/parameters/develop/db.yaml.ejs +21 -0
  37. package/templates/base/resources/parameters/develop/kafka.yml.ejs +26 -0
  38. package/templates/base/resources/parameters/local/cors.yml.ejs +4 -0
  39. package/templates/base/resources/parameters/local/db.yaml.ejs +21 -0
  40. package/templates/base/resources/parameters/local/kafka.yml.ejs +26 -0
  41. package/templates/base/resources/parameters/production/cors.yml.ejs +4 -0
  42. package/templates/base/resources/parameters/production/db.yaml.ejs +21 -0
  43. package/templates/base/resources/parameters/production/kafka.yml.ejs +26 -0
  44. package/templates/base/root/README.md.ejs +126 -0
  45. package/templates/base/root/gitignore.ejs +42 -0
  46. package/templates/http-exchange/Adapter.java.ejs +39 -0
  47. package/templates/http-exchange/Config.java.ejs +24 -0
  48. package/templates/http-exchange/FeignClient.java.ejs +23 -0
  49. package/templates/http-exchange/Port.java.ejs +14 -0
  50. package/templates/kafka-event/Event.java.ejs +10 -0
  51. package/templates/kafka-event/KafkaConfigBean.java.ejs +7 -0
  52. package/templates/kafka-event/KafkaMessageBroker.java.ejs +32 -0
  53. package/templates/kafka-event/MessageBroker.java.ejs +8 -0
  54. package/templates/kafka-event/MessageBrokerImplMethod.java.ejs +9 -0
  55. package/templates/kafka-event/MessageBrokerMethod.java.ejs +1 -0
  56. package/templates/kafka-listener/KafkaController.java.ejs +34 -0
  57. package/templates/kafka-listener/ListenerMethod.java.ejs +9 -0
  58. package/templates/kafka-listener/ValueField.java.ejs +4 -0
  59. package/templates/module/controller.java.ejs +96 -0
  60. package/templates/module/exception.java.ejs +18 -0
  61. package/templates/module/mapper.java.ejs +67 -0
  62. package/templates/module/model.java.ejs +29 -0
  63. package/templates/module/package-info.java.ejs +7 -0
  64. package/templates/module/repository.java.ejs +26 -0
  65. package/templates/module/request-dto.java.ejs +24 -0
  66. package/templates/module/response-dto.java.ejs +26 -0
  67. package/templates/module/service-impl.java.ejs +112 -0
  68. package/templates/module/service.java.ejs +45 -0
  69. package/templates/module/update-dto.java.ejs +25 -0
  70. package/templates/resource/Command.java.ejs +9 -0
  71. package/templates/resource/CommandHandler.java.ejs +22 -0
  72. package/templates/resource/Controller.java.ejs +73 -0
  73. package/templates/resource/Query.java.ejs +12 -0
  74. package/templates/resource/QueryHandler.java.ejs +31 -0
  75. package/templates/resource/ResponseDto.java.ejs +6 -0
  76. package/templates/shared/annotations/ApplicationComponent.java.ejs +9 -0
  77. package/templates/shared/annotations/DomainComponent.java.ejs +9 -0
  78. package/templates/shared/annotations/LogAfter.java.ejs +11 -0
  79. package/templates/shared/annotations/LogBefore.java.ejs +11 -0
  80. package/templates/shared/annotations/LogExceptions.java.ejs +11 -0
  81. package/templates/shared/annotations/LogTimer.java.ejs +11 -0
  82. package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +49 -0
  83. package/templates/shared/configurations/loggerConfig/HandlerLogs.java.ejs +56 -0
  84. package/templates/shared/configurations/securityConfig/SecurityConfig.java.ejs +57 -0
  85. package/templates/shared/configurations/swaggerConfig/SwaggerConfig.java.ejs +31 -0
  86. package/templates/shared/configurations/useCaseConfig/UseCaseAutoRegister.java.ejs +51 -0
  87. package/templates/shared/configurations/useCaseConfig/UseCaseConfig.java.ejs +25 -0
  88. package/templates/shared/configurations/useCaseConfig/UseCaseContainer.java.ejs +29 -0
  89. package/templates/shared/configurations/useCaseConfig/UseCaseMediator.java.ejs +38 -0
  90. package/templates/shared/customExceptions/BadRequestException.java.ejs +11 -0
  91. package/templates/shared/customExceptions/ConflictException.java.ejs +8 -0
  92. package/templates/shared/customExceptions/ForbiddenException.java.ejs +8 -0
  93. package/templates/shared/customExceptions/ImportFileException.java.ejs +6 -0
  94. package/templates/shared/customExceptions/NotFoundException.java.ejs +8 -0
  95. package/templates/shared/customExceptions/UnauthorizedException.java.ejs +9 -0
  96. package/templates/shared/customExceptions/ValidationException.java.ejs +17 -0
  97. package/templates/shared/errorMessage/ErrorMessage.java.ejs +5 -0
  98. package/templates/shared/errorMessage/FullErrorMessage.java.ejs +9 -0
  99. package/templates/shared/errorMessage/ShortErrorMessage.java.ejs +6 -0
  100. package/templates/shared/eventEnvelope/EventEnvelope.java.ejs +13 -0
  101. package/templates/shared/eventEnvelope/EventMetadata.java.ejs +24 -0
  102. package/templates/shared/filters/CorrelationIdFilter.java.ejs +45 -0
  103. package/templates/shared/handlerException/HandlerExceptions.java.ejs +148 -0
  104. package/templates/shared/interfaces/Command.java.ejs +4 -0
  105. package/templates/shared/interfaces/CommandHandler.java.ejs +5 -0
  106. package/templates/shared/interfaces/Dispatchable.java.ejs +4 -0
  107. package/templates/shared/interfaces/Handler.java.ejs +4 -0
  108. package/templates/shared/interfaces/Query.java.ejs +4 -0
  109. package/templates/shared/interfaces/QueryHandler.java.ejs +5 -0
  110. package/templates/shared/package-info.java.ejs +8 -0
  111. package/templates/usecase/command/Command.java.ejs +7 -0
  112. package/templates/usecase/command/CommandHandler.java.ejs +21 -0
  113. package/templates/usecase/query/Query.java.ejs +10 -0
  114. package/templates/usecase/query/QueryHandler.java.ejs +22 -0
  115. 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;