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,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;
|