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,453 @@
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 yaml = require('js-yaml');
7
+ const ConfigManager = require('../utils/config-manager');
8
+ const { isEva4jProject, moduleExists } = require('../utils/validator');
9
+ const { toPackagePath, toPascalCase, toSnakeCase, toKebabCase } = require('../utils/naming');
10
+ const { renderAndWrite, renderTemplate } = require('../utils/template-engine');
11
+
12
+ async function generateKafkaEventCommand(moduleName, eventName) {
13
+ const projectDir = process.cwd();
14
+
15
+ // Validate we're in an eva4j project
16
+ if (!(await isEva4jProject(projectDir))) {
17
+ console.error(chalk.red('❌ Not in an eva4j project directory'));
18
+ console.error(chalk.gray('Run this command inside a project created with eva4j'));
19
+ process.exit(1);
20
+ }
21
+
22
+ // Check if Kafka is installed
23
+ const configManager = new ConfigManager(projectDir);
24
+ if (!(await configManager.featureExists('kafka'))) {
25
+ console.error(chalk.red('❌ Kafka client is not installed in this project'));
26
+ console.error(chalk.gray('Install Kafka first using: eva4j add kafka-client'));
27
+ process.exit(1);
28
+ }
29
+
30
+ // Load project configuration
31
+ const projectConfig = await configManager.loadProjectConfig();
32
+
33
+ if (!projectConfig) {
34
+ console.error(chalk.red('❌ Could not load project configuration'));
35
+ console.error(chalk.gray('Make sure .eva4j.json exists in the project root'));
36
+ process.exit(1);
37
+ }
38
+
39
+ const { packageName } = projectConfig;
40
+ const packagePath = toPackagePath(packageName);
41
+
42
+ // Validate module exists
43
+ if (!(await configManager.moduleExists(moduleName))) {
44
+ console.error(chalk.red(`❌ Module '${moduleName}' not found in project configuration`));
45
+ console.error(chalk.gray('Create the module first using: eva4j add module <name>'));
46
+ process.exit(1);
47
+ }
48
+
49
+ if (!(await moduleExists(projectDir, packagePath, moduleName))) {
50
+ console.error(chalk.red(`❌ Module '${moduleName}' does not exist in filesystem`));
51
+ process.exit(1);
52
+ }
53
+
54
+ // Prompt for event name if not provided
55
+ if (!eventName) {
56
+ const nameAnswer = await inquirer.prompt([
57
+ {
58
+ type: 'input',
59
+ name: 'eventName',
60
+ message: 'Enter event name:',
61
+ validate: (input) => {
62
+ if (!input || input.trim() === '') {
63
+ return 'Event name cannot be empty';
64
+ }
65
+ return true;
66
+ }
67
+ }
68
+ ]);
69
+ eventName = nameAnswer.eventName;
70
+ }
71
+
72
+ // Normalize event name to PascalCase
73
+ const normalizedEventName = toPascalCase(eventName);
74
+ const eventClassName = normalizedEventName.endsWith('Event')
75
+ ? normalizedEventName
76
+ : `${normalizedEventName}Event`;
77
+
78
+ // Check if event already exists
79
+ const eventPath = path.join(projectDir, 'src', 'main', 'java', packagePath, moduleName, 'application', 'events', `${eventClassName}.java`);
80
+ if (await fs.pathExists(eventPath)) {
81
+ console.error(chalk.red(`❌ Event '${eventClassName}' already exists in module '${moduleName}'`));
82
+ process.exit(1);
83
+ }
84
+
85
+ // Prompt for event configuration
86
+ const answers = await inquirer.prompt([
87
+ {
88
+ type: 'number',
89
+ name: 'partitions',
90
+ message: 'Number of partitions:',
91
+ default: 3,
92
+ validate: (value) => {
93
+ if (value < 1) return 'Partitions must be at least 1';
94
+ return true;
95
+ }
96
+ },
97
+ {
98
+ type: 'number',
99
+ name: 'replicas',
100
+ message: 'Number of replicas:',
101
+ default: 1,
102
+ validate: (value) => {
103
+ if (value < 1) return 'Replicas must be at least 1';
104
+ return true;
105
+ }
106
+ }
107
+ ]);
108
+
109
+ const { partitions, replicas } = answers;
110
+
111
+ const spinner = ora('Generating Kafka event...').start();
112
+
113
+ try {
114
+ // Generate property names
115
+ const topicNameKebab = toKebabCase(eventName);
116
+ const topicNameSnake = toSnakeCase(eventName).toUpperCase();
117
+ const topicPropertyKey = topicNameKebab;
118
+ const topicPropertyValue = topicNameSnake;
119
+ const topicSpringProperty = `\${topics.${topicNameKebab}}`;
120
+
121
+ const context = {
122
+ packageName,
123
+ moduleName,
124
+ eventClassName,
125
+ topicNameSnake,
126
+ topicNameKebab,
127
+ topicPropertyKey,
128
+ topicPropertyValue,
129
+ topicSpringProperty,
130
+ partitions,
131
+ replicas
132
+ };
133
+
134
+ // 1. Generate Event Record
135
+ spinner.text = 'Generating event record...';
136
+ await generateEventRecord(projectDir, packagePath, context);
137
+
138
+ // 2. Update kafka.yml files
139
+ spinner.text = 'Updating kafka.yml configuration...';
140
+ await updateKafkaYml(projectDir, topicPropertyKey, topicPropertyValue);
141
+
142
+ // 3. Create/Update MessageBroker interface
143
+ spinner.text = 'Updating MessageBroker interface...';
144
+ await createOrUpdateMessageBroker(projectDir, packagePath, context);
145
+
146
+ // 4. Create/Update KafkaMessageBroker implementation
147
+ spinner.text = 'Updating KafkaMessageBroker implementation...';
148
+ await createOrUpdateKafkaMessageBroker(projectDir, packagePath, context);
149
+
150
+ // 5. Update KafkaConfig with NewTopic bean
151
+ spinner.text = 'Updating KafkaConfig...';
152
+ await updateKafkaConfig(projectDir, packagePath, context);
153
+
154
+ spinner.succeed(chalk.green('Kafka event generated successfully! ✨'));
155
+
156
+ console.log(chalk.blue('\n📦 Generated/Updated components:'));
157
+ console.log(chalk.gray(` ├── ${moduleName}/application/events/${eventClassName}.java`));
158
+ console.log(chalk.gray(` ├── ${moduleName}/application/ports/MessageBroker.java`));
159
+ console.log(chalk.gray(` ├── ${moduleName}/infrastructure/adapters/kafkaMessageBroker/KafkaMessageBroker.java`));
160
+ console.log(chalk.gray(` ├── shared/configurations/kafkaConfig/KafkaConfig.java`));
161
+ console.log(chalk.gray(' └── parameters/*/kafka.yml (all environments)'));
162
+
163
+ console.log(chalk.blue('\n✅ Kafka event configured successfully!'));
164
+ console.log(chalk.white(`\n Event: ${eventClassName}`));
165
+ console.log(chalk.white(` Topic: ${topicPropertyValue} (${topicNameKebab})`));
166
+ console.log(chalk.white(` Partitions: ${partitions}`));
167
+ console.log(chalk.white(` Replicas: ${replicas}`));
168
+ console.log(chalk.gray('\n You can now inject MessageBroker in your services and call:'));
169
+ console.log(chalk.gray(` messageBroker.publish${eventClassName}(event);\n`));
170
+
171
+ } catch (error) {
172
+ spinner.fail(chalk.red('Failed to generate Kafka event'));
173
+ console.error(chalk.red('\n❌ Error:'), error.message);
174
+ if (error.stack) {
175
+ console.error(chalk.gray(error.stack));
176
+ }
177
+ process.exit(1);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Generate Event Record
183
+ */
184
+ async function generateEventRecord(projectDir, packagePath, context) {
185
+ const templatePath = path.join(__dirname, '..', '..', 'templates', 'kafka-event', 'Event.java.ejs');
186
+ const outputPath = path.join(
187
+ projectDir, 'src', 'main', 'java', packagePath,
188
+ context.moduleName, 'application', 'events', `${context.eventClassName}.java`
189
+ );
190
+
191
+ await renderAndWrite(templatePath, outputPath, context);
192
+ }
193
+
194
+ /**
195
+ * Update kafka.yml files in all environments
196
+ */
197
+ async function updateKafkaYml(projectDir, topicKey, topicValue) {
198
+ const environments = ['local', 'develop', 'test', 'production'];
199
+
200
+ for (const env of environments) {
201
+ const kafkaYmlPath = path.join(projectDir, 'src', 'main', 'resources', 'parameters', env, 'kafka.yml');
202
+
203
+ if (!(await fs.pathExists(kafkaYmlPath))) {
204
+ continue;
205
+ }
206
+
207
+ let kafkaContent = {};
208
+ const existingContent = await fs.readFile(kafkaYmlPath, 'utf8');
209
+ kafkaContent = yaml.load(existingContent) || {};
210
+
211
+ // Initialize topics section if it doesn't exist
212
+ if (!kafkaContent.topics) {
213
+ kafkaContent.topics = {};
214
+ }
215
+
216
+ // Add new topic if it doesn't exist
217
+ if (!kafkaContent.topics[topicKey]) {
218
+ kafkaContent.topics[topicKey] = topicValue;
219
+
220
+ // Write back to file
221
+ const yamlContent = yaml.dump(kafkaContent, {
222
+ indent: 2,
223
+ lineWidth: -1,
224
+ quotingType: '"',
225
+ forceQuotes: false
226
+ });
227
+
228
+ await fs.writeFile(kafkaYmlPath, yamlContent, 'utf8');
229
+ }
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Create or update MessageBroker interface
235
+ */
236
+ async function createOrUpdateMessageBroker(projectDir, packagePath, context) {
237
+ const interfacePath = path.join(
238
+ projectDir, 'src', 'main', 'java', packagePath,
239
+ context.moduleName, 'application', 'ports', 'MessageBroker.java'
240
+ );
241
+
242
+ const methodName = `publish${context.eventClassName}`;
243
+
244
+ if (await fs.pathExists(interfacePath)) {
245
+ // Update existing interface
246
+ let content = await fs.readFile(interfacePath, 'utf-8');
247
+
248
+ // Check if method already exists
249
+ if (content.includes(methodName)) {
250
+ return; // Method already exists
251
+ }
252
+
253
+ // Check if import exists and add it if needed
254
+ const importStatement = `import ${context.packageName}.${context.moduleName}.application.events.${context.eventClassName};`;
255
+ if (!content.includes(importStatement)) {
256
+ // Find the position after the package declaration
257
+ const packageMatch = content.match(/(package\s+[\w.]+;\s*\n)/);
258
+ if (packageMatch) {
259
+ const insertPos = packageMatch.index + packageMatch[0].length;
260
+
261
+ // Check if there are already imports
262
+ const hasImports = /import\s+[\w.]+;/.test(content);
263
+
264
+ if (hasImports) {
265
+ // Find the position after the last import
266
+ const imports = content.matchAll(/import\s+[\w.]+;\s*\n/g);
267
+ let lastImportEnd = insertPos;
268
+ for (const match of imports) {
269
+ lastImportEnd = match.index + match[0].length;
270
+ }
271
+ // Insert the new import after the last import
272
+ content = content.slice(0, lastImportEnd) + importStatement + '\n' + content.slice(lastImportEnd);
273
+ } else {
274
+ // No imports yet, add after package with blank line
275
+ content = content.slice(0, insertPos) + '\n' + importStatement + '\n' + content.slice(insertPos);
276
+ }
277
+ }
278
+ }
279
+
280
+ // Generate method signature
281
+ const templatePath = path.join(__dirname, '..', '..', 'templates', 'kafka-event', 'MessageBrokerMethod.java.ejs');
282
+ const methodSignature = await renderTemplate(templatePath, context);
283
+
284
+ // Find the last closing brace and insert before it
285
+ const lastBraceIndex = content.lastIndexOf('}');
286
+ if (lastBraceIndex === -1) {
287
+ throw new Error('Could not find closing brace in MessageBroker interface');
288
+ }
289
+
290
+ content = content.slice(0, lastBraceIndex) + '\n' + methodSignature + '\n}\n';
291
+ await fs.writeFile(interfacePath, content, 'utf-8');
292
+ } else {
293
+ // Create new interface
294
+ const templatePath = path.join(__dirname, '..', '..', 'templates', 'kafka-event', 'MessageBroker.java.ejs');
295
+ await renderAndWrite(templatePath, interfacePath, context);
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Create or update KafkaMessageBroker implementation
301
+ */
302
+ async function createOrUpdateKafkaMessageBroker(projectDir, packagePath, context) {
303
+ const adapterPath = path.join(
304
+ projectDir, 'src', 'main', 'java', packagePath,
305
+ context.moduleName, 'infrastructure', 'adapters', 'kafkaMessageBroker', 'KafkaMessageBroker.java'
306
+ );
307
+
308
+ const methodName = `publish${context.eventClassName}`;
309
+
310
+ if (await fs.pathExists(adapterPath)) {
311
+ // Update existing implementation
312
+ let content = await fs.readFile(adapterPath, 'utf-8');
313
+
314
+ // Check if method already exists
315
+ if (content.includes(methodName)) {
316
+ return; // Method already exists
317
+ }
318
+
319
+ // Check if event import exists and add it if needed
320
+ const eventImport = `import ${context.packageName}.${context.moduleName}.application.events.${context.eventClassName};`;
321
+ if (!content.includes(eventImport)) {
322
+ const packageMatch = content.match(/(package\s+[\w.]+;\s*\n)/);
323
+ if (packageMatch) {
324
+ const insertPos = packageMatch.index + packageMatch[0].length;
325
+ const hasImports = /import\s+[\w.]+;/.test(content);
326
+
327
+ if (hasImports) {
328
+ const imports = content.matchAll(/import\s+[\w.]+;\s*\n/g);
329
+ let lastImportEnd = insertPos;
330
+ for (const match of imports) {
331
+ lastImportEnd = match.index + match[0].length;
332
+ }
333
+ content = content.slice(0, lastImportEnd) + eventImport + '\n' + content.slice(lastImportEnd);
334
+ } else {
335
+ content = content.slice(0, insertPos) + '\n' + eventImport + '\n' + content.slice(insertPos);
336
+ }
337
+ }
338
+ }
339
+
340
+ // Check if EventEnvelope import exists
341
+ const envelopeImport = `import ${context.packageName}.shared.infrastructure.eventEnvelope.EventEnvelope;`;
342
+ if (!content.includes(envelopeImport)) {
343
+ const packageMatch = content.match(/(package\s+[\w.]+;\s*\n)/);
344
+ if (packageMatch) {
345
+ const insertPos = packageMatch.index + packageMatch[0].length;
346
+ const imports = content.matchAll(/import\s+[\w.]+;\s*\n/g);
347
+ let lastImportEnd = insertPos;
348
+ for (const match of imports) {
349
+ lastImportEnd = match.index + match[0].length;
350
+ }
351
+ content = content.slice(0, lastImportEnd) + envelopeImport + '\n' + content.slice(lastImportEnd);
352
+ }
353
+ }
354
+
355
+ // Check if @Value import exists
356
+ const valueImport = 'import org.springframework.beans.factory.annotation.Value;';
357
+ if (!content.includes(valueImport)) {
358
+ const packageMatch = content.match(/(package\s+[\w.]+;\s*\n)/);
359
+ if (packageMatch) {
360
+ const insertPos = packageMatch.index + packageMatch[0].length;
361
+ const imports = content.matchAll(/import\s+[\w.]+;\s*\n/g);
362
+ let lastImportEnd = insertPos;
363
+ for (const match of imports) {
364
+ lastImportEnd = match.index + match[0].length;
365
+ }
366
+ content = content.slice(0, lastImportEnd) + valueImport + '\n' + content.slice(lastImportEnd);
367
+ }
368
+ }
369
+
370
+ // Check if @Value field exists for this topic
371
+ const valueFieldName = `${context.topicNameKebab.replace(/-/g, '')}Topic`;
372
+ if (!content.includes(`private String ${valueFieldName};`)) {
373
+ // Find the last @Value field and add after it, or after class declaration
374
+ const valueFieldPattern = /(@Value\([^)]+\)\s*\n\s*private\s+String\s+\w+Topic;\s*\n)/g;
375
+ const valueFields = [...content.matchAll(valueFieldPattern)];
376
+
377
+ if (valueFields.length > 0) {
378
+ // Add after the last @Value field
379
+ const lastField = valueFields[valueFields.length - 1];
380
+ const insertPos = lastField.index + lastField[0].length;
381
+ content = content.slice(0, insertPos) +
382
+ `\n @Value("${context.topicSpringProperty}")\n private String ${valueFieldName};\n\n` +
383
+ content.slice(insertPos);
384
+ } else {
385
+ // Add after class declaration
386
+ const classPattern = /(public\s+class\s+KafkaMessageBroker\s+implements\s+MessageBroker\s*\{\s*\n)/;
387
+ const classMatch = content.match(classPattern);
388
+ if (classMatch) {
389
+ const insertPos = classMatch.index + classMatch[0].length;
390
+ content = content.slice(0, insertPos) +
391
+ `\n @Value("${context.topicSpringProperty}")\n private String ${valueFieldName};\n` +
392
+ content.slice(insertPos);
393
+ }
394
+ }
395
+ }
396
+
397
+ // Generate method implementation
398
+ const templatePath = path.join(__dirname, '..', '..', 'templates', 'kafka-event', 'MessageBrokerImplMethod.java.ejs');
399
+ const methodImpl = await renderTemplate(templatePath, { ...context, valueFieldName });
400
+
401
+ // Find the last closing brace and insert before it
402
+ const lastBraceIndex = content.lastIndexOf('}');
403
+ if (lastBraceIndex === -1) {
404
+ throw new Error('Could not find closing brace in KafkaMessageBroker class');
405
+ }
406
+
407
+ content = content.slice(0, lastBraceIndex) + '\n' + methodImpl + '\n}\n';
408
+ await fs.writeFile(adapterPath, content, 'utf-8');
409
+ } else {
410
+ // Create new implementation
411
+ const templatePath = path.join(__dirname, '..', '..', 'templates', 'kafka-event', 'KafkaMessageBroker.java.ejs');
412
+ await renderAndWrite(templatePath, adapterPath, context);
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Update KafkaConfig with NewTopic bean
418
+ */
419
+ async function updateKafkaConfig(projectDir, packagePath, context) {
420
+ const configPath = path.join(
421
+ projectDir, 'src', 'main', 'java', packagePath,
422
+ 'shared', 'infrastructure', 'configurations', 'kafkaConfig', 'KafkaConfig.java'
423
+ );
424
+
425
+ if (!(await fs.pathExists(configPath))) {
426
+ throw new Error('KafkaConfig.java not found. Please install Kafka first using: eva4j add kafka-client');
427
+ }
428
+
429
+ let content = await fs.readFile(configPath, 'utf-8');
430
+
431
+ const beanMethodName = `${context.topicNameKebab.replace(/-/g, '')}Topic`;
432
+
433
+ // Check if bean already exists
434
+ if (content.includes(`public NewTopic ${beanMethodName}(`)) {
435
+ return; // Bean already exists
436
+ }
437
+
438
+ // Generate bean method
439
+ const templatePath = path.join(__dirname, '..', '..', 'templates', 'kafka-event', 'KafkaConfigBean.java.ejs');
440
+ const valueFieldName = `${context.topicNameKebab.replace(/-/g, '')}Topic`;
441
+ const beanMethod = await renderTemplate(templatePath, { ...context, beanMethodName, valueFieldName });
442
+
443
+ // Find the last closing brace and insert before it
444
+ const lastBraceIndex = content.lastIndexOf('}');
445
+ if (lastBraceIndex === -1) {
446
+ throw new Error('Could not find closing brace in KafkaConfig class');
447
+ }
448
+
449
+ content = content.slice(0, lastBraceIndex) + '\n' + beanMethod + '\n}\n';
450
+ await fs.writeFile(configPath, content, 'utf-8');
451
+ }
452
+
453
+ module.exports = generateKafkaEventCommand;