eva4j 1.0.16 → 1.0.18

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 (151) hide show
  1. package/AGENTS.md +220 -5
  2. package/DOMAIN_YAML_GUIDE.md +188 -3
  3. package/FUTURE_FEATURES.md +33 -52
  4. package/QUICK_REFERENCE.md +8 -4
  5. package/bin/eva4j.js +70 -2
  6. package/config/defaults.json +1 -0
  7. package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
  8. package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
  9. package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
  10. package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
  11. package/docs/commands/EVALUATE_SYSTEM.md +290 -10
  12. package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
  13. package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
  14. package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
  15. package/docs/commands/INDEX.md +27 -3
  16. package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
  17. package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
  18. package/docs/prototype/system/RISKS.md +277 -0
  19. package/docs/prototype/system/customers.yaml +133 -0
  20. package/docs/prototype/system/inventory.yaml +109 -0
  21. package/docs/prototype/system/notifications.yaml +131 -0
  22. package/docs/prototype/system/orders.yaml +241 -0
  23. package/docs/prototype/system/payments.yaml +256 -0
  24. package/docs/prototype/system/products.yaml +168 -0
  25. package/docs/prototype/system/system.yaml +269 -0
  26. package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
  27. package/examples/domain-events.yaml +26 -0
  28. package/examples/domain-read-models.yaml +113 -0
  29. package/examples/system/customer.yaml +89 -0
  30. package/examples/system/orders.yaml +119 -0
  31. package/examples/system/product.yaml +27 -0
  32. package/examples/system/system.yaml +80 -0
  33. package/package.json +1 -1
  34. package/read-model-spec.md +664 -0
  35. package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
  36. package/src/agents/design-gap-analyst.agent.md +383 -0
  37. package/src/agents/design-reviewer-temporal.agent.md +412 -0
  38. package/src/agents/design-reviewer.agent.md +34 -5
  39. package/src/agents/implement-use-cases.prompt.md +179 -0
  40. package/src/agents/ux-gap-analyst.agent.md +412 -0
  41. package/src/commands/add-rabbitmq-client.js +261 -0
  42. package/src/commands/add-temporal-client.js +22 -2
  43. package/src/commands/build.js +267 -11
  44. package/src/commands/evaluate-system.js +700 -13
  45. package/src/commands/generate-entities.js +560 -24
  46. package/src/commands/generate-http-exchange.js +3 -0
  47. package/src/commands/generate-kafka-event.js +3 -0
  48. package/src/commands/generate-kafka-listener.js +3 -0
  49. package/src/commands/generate-rabbitmq-event.js +665 -0
  50. package/src/commands/generate-rabbitmq-listener.js +205 -0
  51. package/src/commands/generate-record.js +2 -2
  52. package/src/commands/generate-resource.js +4 -1
  53. package/src/commands/generate-temporal-activity.js +970 -33
  54. package/src/commands/generate-temporal-flow.js +98 -38
  55. package/src/commands/generate-temporal-system.js +708 -0
  56. package/src/commands/generate-usecase.js +4 -1
  57. package/src/skills/build-system-yaml/SKILL.md +343 -2
  58. package/src/skills/build-system-yaml/references/domain-yaml-spec.md +253 -26
  59. package/src/skills/build-system-yaml/references/module-spec.md +90 -9
  60. package/src/skills/build-system-yaml/references/system-yaml-spec.md +36 -0
  61. package/src/skills/build-temporal-system/SKILL.md +752 -0
  62. package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
  63. package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
  64. package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
  65. package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
  66. package/src/skills/implement-use-case/SKILL.md +350 -0
  67. package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
  68. package/src/skills/requirements-elicitation/SKILL.md +228 -0
  69. package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
  70. package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
  71. package/src/utils/bounded-context-diagram.js +844 -0
  72. package/src/utils/config-manager.js +4 -2
  73. package/src/utils/domain-validator.js +495 -17
  74. package/src/utils/naming.js +20 -0
  75. package/src/utils/system-validator.js +169 -11
  76. package/src/utils/system-yaml-parser.js +318 -0
  77. package/src/utils/temporal-validator.js +497 -0
  78. package/src/utils/validator.js +3 -1
  79. package/src/utils/yaml-to-entity.js +281 -9
  80. package/templates/aggregate/AggregateRepository.java.ejs +4 -0
  81. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +8 -0
  82. package/templates/aggregate/AggregateRoot.java.ejs +38 -4
  83. package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
  84. package/templates/aggregate/JpaAggregateRoot.java.ejs +4 -4
  85. package/templates/aggregate/JpaEntity.java.ejs +2 -2
  86. package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
  87. package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
  88. package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
  89. package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
  90. package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
  91. package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
  92. package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
  93. package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
  94. package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
  95. package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
  96. package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
  97. package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
  98. package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
  99. package/templates/base/root/AGENTS.md.ejs +1 -1
  100. package/templates/crud/DeleteCommandHandler.java.ejs +19 -1
  101. package/templates/crud/EndpointsController.java.ejs +1 -1
  102. package/templates/crud/ScaffoldCommand.java.ejs +5 -2
  103. package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
  104. package/templates/crud/ScaffoldQuery.java.ejs +5 -2
  105. package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
  106. package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
  107. package/templates/crud/UpdateCommandHandler.java.ejs +53 -2
  108. package/templates/evaluate/report.html.ejs +1447 -90
  109. package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
  110. package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
  111. package/templates/ports/PortAclMapper.java.ejs +35 -0
  112. package/templates/ports/PortFeignAdapter.java.ejs +7 -22
  113. package/templates/ports/PortFeignClient.java.ejs +4 -0
  114. package/templates/ports/PortResponseDto.java.ejs +1 -1
  115. package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
  116. package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
  117. package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
  118. package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
  119. package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
  120. package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
  121. package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
  122. package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
  123. package/templates/read-model/ReadModelDomain.java.ejs +46 -0
  124. package/templates/read-model/ReadModelJpa.java.ejs +58 -0
  125. package/templates/read-model/ReadModelJpaRepository.java.ejs +13 -0
  126. package/templates/read-model/ReadModelKafkaListener.java.ejs +64 -0
  127. package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
  128. package/templates/read-model/ReadModelRepository.java.ejs +42 -0
  129. package/templates/read-model/ReadModelRepositoryImpl.java.ejs +85 -0
  130. package/templates/read-model/ReadModelSyncHandler.java.ejs +54 -0
  131. package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
  132. package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
  133. package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
  134. package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
  135. package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
  136. package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
  137. package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
  138. package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
  139. package/templates/temporal-activity/NestedType.java.ejs +12 -0
  140. package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
  141. package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
  142. package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
  143. package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
  144. package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
  145. package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
  146. package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
  147. package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
  148. package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
  149. package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
  150. package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
  151. package/COMMAND_EVALUATION.md +0 -911
@@ -80,6 +80,10 @@ async function addTemporalClientCommand() {
80
80
  spinner.text = 'Generating TemporalConfig class...';
81
81
  await generateTemporalConfigClass(projectDir, context);
82
82
 
83
+ // 5b. Generate TemporalWorkerFactoryLifecycle.java
84
+ spinner.text = 'Generating TemporalWorkerFactoryLifecycle class...';
85
+ await generateTemporalLifecycleClass(projectDir, context);
86
+
83
87
  // 6. Update docker-compose.yaml if it exists
84
88
  spinner.text = 'Updating docker-compose.yaml...';
85
89
  await updateDockerCompose(projectDir, context);
@@ -99,13 +103,14 @@ async function addTemporalClientCommand() {
99
103
  console.log(chalk.gray(' │ └── production/temporal.yaml'));
100
104
  console.log(chalk.gray(' ├── shared/domain/interfaces/HeavyActivity.java'));
101
105
  console.log(chalk.gray(' ├── shared/domain/interfaces/LightActivity.java'));
102
- console.log(chalk.gray(' └── shared/infrastructure/configurations/temporalConfig/TemporalConfig.java'));
106
+ console.log(chalk.gray(' ├── shared/infrastructure/configurations/temporalConfig/TemporalConfig.java'));
107
+ console.log(chalk.gray(' └── shared/infrastructure/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java'));
103
108
 
104
109
  console.log(chalk.blue('\n✅ Temporal client configured successfully!'));
105
110
  console.log(chalk.white('\n Service URL: localhost:7233'));
106
111
  console.log(chalk.white(' Namespace: default'));
107
112
  console.log(chalk.white(' Temporal UI: http://localhost:8088'));
108
- console.log(chalk.yellow('\n ⚠️ Register your workflow implementation types in TemporalConfig.java'));
113
+ console.log(chalk.yellow('\n ⚠️ Run "eva g temporal-flow <module>" to create workflows with module-scoped queues'));
109
114
  console.log(chalk.gray(' Run "docker-compose up -d" to start the Temporal cluster'));
110
115
  console.log(chalk.gray(' Update temporal.yaml files to customize service URLs per environment'));
111
116
  console.log();
@@ -235,6 +240,21 @@ async function generateTemporalConfigClass(projectDir, context) {
235
240
  await renderAndWrite(templatePath, outputPath, context);
236
241
  }
237
242
 
243
+ /**
244
+ * Generate TemporalWorkerFactoryLifecycle.java class
245
+ */
246
+ async function generateTemporalLifecycleClass(projectDir, context) {
247
+ const templatePath = path.join(
248
+ __dirname, '..', '..', 'templates', 'shared', 'configurations', 'temporalConfig', 'TemporalWorkerFactoryLifecycle.java.ejs'
249
+ );
250
+ const outputPath = path.join(
251
+ projectDir, 'src', 'main', 'java', context.packagePath,
252
+ 'shared', 'infrastructure', 'configurations', 'temporalConfig', 'TemporalWorkerFactoryLifecycle.java'
253
+ );
254
+
255
+ await renderAndWrite(templatePath, outputPath, context);
256
+ }
257
+
238
258
  /**
239
259
  * Update docker-compose.yaml to add Temporal services
240
260
  */
@@ -7,13 +7,18 @@ const yaml = require('js-yaml');
7
7
 
8
8
  const ConfigManager = require('../utils/config-manager');
9
9
  const { isEva4jProject } = require('../utils/validator');
10
- const { toCamelCase, toPackagePath } = require('../utils/naming');
10
+ const { toCamelCase, toPascalCase, toScreamingSnakeCase, toPackagePath } = require('../utils/naming');
11
11
  const SharedGenerator = require('../generators/shared-generator');
12
12
  const { renderAndWrite } = require('../utils/template-engine');
13
13
  const addModuleCommand = require('./add-module');
14
14
  const addKafkaClientCommand = require('./add-kafka-client');
15
+ const addRabbitMQClientCommand = require('./add-rabbitmq-client');
16
+ const addTemporalClientCommand = require('./add-temporal-client');
15
17
  const generateEntitiesCommand = require('./generate-entities');
18
+ const generateTemporalSystemCommand = require('./generate-temporal-system');
19
+ const generateTemporalActivityCommand = require('./generate-temporal-activity');
16
20
  const { generateUnifiedPostmanCollection } = require('../generators/postman-generator');
21
+ const ChecksumManager = require('../utils/checksum-manager');
17
22
 
18
23
  // ── H2 mock config ─────────────────────────────────────────────────────────────
19
24
  const H2_DB_YAML = (packageName) => `spring:
@@ -244,7 +249,16 @@ async function swapToH2(projectDir, packageName, configManager) {
244
249
  if (await fs.pathExists(kafkaConfigPath)) {
245
250
  backups.kafkaConfig = { path: kafkaConfigPath, content: await fs.readFile(kafkaConfigPath, 'utf-8') };
246
251
  }
247
- // When Kafka is not installed, omit the key entirely — restoreFromH2() iterates
252
+
253
+ // ── RabbitMQConfig.java — backup so it can be restored later ──────────────
254
+ const rabbitConfigPath = path.join(
255
+ projectDir, 'src', 'main', 'java', packagePath,
256
+ 'shared', 'infrastructure', 'configurations', 'rabbitmqConfig', 'RabbitMQConfig.java'
257
+ );
258
+ if (await fs.pathExists(rabbitConfigPath)) {
259
+ backups.rabbitConfig = { path: rabbitConfigPath, content: await fs.readFile(rabbitConfigPath, 'utf-8') };
260
+ }
261
+ // When broker is not installed, omit the key entirely — restoreFromH2() iterates
248
262
  // Object.values(backups) and would crash trying to destructure a null entry.
249
263
 
250
264
  // Persist backups BEFORE writing any file so a crash mid-swap is recoverable
@@ -267,6 +281,11 @@ async function swapToH2(projectDir, packageName, configManager) {
267
281
  /\n?[ \t]*\/\/ Kafka\n[ \t]*implementation 'org\.springframework\.kafka:spring-kafka'\n[ \t]*testImplementation 'org\.springframework\.kafka:spring-kafka-test'\n\n?[ \t]*/,
268
282
  '\n\t'
269
283
  );
284
+ // Remove spring-amqp dependencies block when RabbitMQ is installed
285
+ swapped = swapped.replace(
286
+ /\n?[ \t]*\/\/ RabbitMQ\n[ \t]*implementation 'org\.springframework\.boot:spring-boot-starter-amqp'\n[ \t]*testImplementation 'org\.springframework\.amqp:spring-rabbit-test'\n\n?[ \t]*/,
287
+ '\n\t'
288
+ );
270
289
  await fs.writeFile(buildGradlePath, swapped, 'utf-8');
271
290
  }
272
291
 
@@ -278,11 +297,16 @@ async function swapToH2(projectDir, packageName, configManager) {
278
297
  await fs.remove(kafkaConfigPath);
279
298
  }
280
299
 
300
+ // ── Remove RabbitMQConfig.java (restored from backup on eva build) ────────
301
+ if (backups.rabbitConfig) {
302
+ await fs.remove(rabbitConfigPath);
303
+ }
304
+
281
305
  return backups;
282
306
  }
283
307
 
284
308
  /**
285
- * Backup and swap ONLY the broker layer (Kafka → Spring Event bus).
309
+ * Backup and swap ONLY the broker layer (Kafka/RabbitMQ → Spring Event bus).
286
310
  * Database config (db.yaml) and SecurityConfig.java are left untouched.
287
311
  * Persists backups to .eva4j.json with _mockOnlyBroker = true.
288
312
  */
@@ -305,15 +329,29 @@ async function swapBrokerOnly(projectDir, packageName, configManager) {
305
329
  backups.kafkaConfig = { path: kafkaConfigPath, content: await fs.readFile(kafkaConfigPath, 'utf-8') };
306
330
  }
307
331
 
332
+ // ── RabbitMQConfig.java ───────────────────────────────────────────────────
333
+ const rabbitConfigPath = path.join(
334
+ projectDir, 'src', 'main', 'java', packagePath,
335
+ 'shared', 'infrastructure', 'configurations', 'rabbitmqConfig', 'RabbitMQConfig.java'
336
+ );
337
+ if (await fs.pathExists(rabbitConfigPath)) {
338
+ backups.rabbitConfig = { path: rabbitConfigPath, content: await fs.readFile(rabbitConfigPath, 'utf-8') };
339
+ }
340
+
308
341
  // Persist backups BEFORE writing any file so a crash mid-swap is recoverable
309
342
  await configManager.saveMockBackup(backups, { onlyBroker: true });
310
343
 
311
344
  // ── Remove spring-kafka from build.gradle (keep existing DB driver line) ──
312
345
  if (backups.buildGradle) {
313
- const swapped = backups.buildGradle.content.replace(
346
+ let swapped = backups.buildGradle.content.replace(
314
347
  /\n?[ \t]*\/\/ Kafka\n[ \t]*implementation 'org\.springframework\.kafka:spring-kafka'\n[ \t]*testImplementation 'org\.springframework\.kafka:spring-kafka-test'\n\n?[ \t]*/,
315
348
  '\n\t'
316
349
  );
350
+ // Remove spring-amqp dependencies block when RabbitMQ is installed
351
+ swapped = swapped.replace(
352
+ /\n?[ \t]*\/\/ RabbitMQ\n[ \t]*implementation 'org\.springframework\.boot:spring-boot-starter-amqp'\n[ \t]*testImplementation 'org\.springframework\.amqp:spring-rabbit-test'\n\n?[ \t]*/,
353
+ '\n\t'
354
+ );
317
355
  await fs.writeFile(buildGradlePath, swapped, 'utf-8');
318
356
  }
319
357
 
@@ -322,6 +360,11 @@ async function swapBrokerOnly(projectDir, packageName, configManager) {
322
360
  await fs.remove(kafkaConfigPath);
323
361
  }
324
362
 
363
+ // ── Remove RabbitMQConfig.java (restored from backup on eva build) ────────
364
+ if (backups.rabbitConfig) {
365
+ await fs.remove(rabbitConfigPath);
366
+ }
367
+
325
368
  return backups;
326
369
  }
327
370
 
@@ -343,6 +386,38 @@ async function restoreFromH2(configManager) {
343
386
  return Object.keys(backups).length;
344
387
  }
345
388
 
389
+ // ── Temporal module queue helper ─────────────────────────────────────────────
390
+ /**
391
+ * Append module-specific queue configuration to temporal.yaml files (idempotent).
392
+ * Same logic as generate-temporal-flow.js appendModuleQueues().
393
+ */
394
+ async function appendTemporalModuleQueues(projectDir, moduleName, moduleScreamingSnake) {
395
+ const resourcesDir = path.join(projectDir, 'src', 'main', 'resources', 'parameters');
396
+
397
+ const moduleSection = [
398
+ ` ${moduleName}:`,
399
+ ` flow-queue: ${moduleScreamingSnake}_WORKFLOW_QUEUE`,
400
+ ` heavy-queue: ${moduleScreamingSnake}_HEAVY_TASK_QUEUE`,
401
+ ` light-queue: ${moduleScreamingSnake}_LIGHT_TASK_QUEUE`,
402
+ ].join('\n');
403
+
404
+ for (const env of ENVS) {
405
+ const yamlPath = path.join(resourcesDir, env, 'temporal.yaml');
406
+ if (!(await fs.pathExists(yamlPath))) continue;
407
+
408
+ let content = await fs.readFile(yamlPath, 'utf-8');
409
+ if (content.includes(`${moduleName}:`)) continue;
410
+
411
+ if (!content.includes('modules:')) {
412
+ content = content.trimEnd() + '\n modules:\n' + moduleSection + '\n';
413
+ } else {
414
+ content = content.trimEnd() + '\n' + moduleSection + '\n';
415
+ }
416
+
417
+ await fs.writeFile(yamlPath, content, 'utf-8');
418
+ }
419
+ }
420
+
346
421
  // ── Main build command ──────────────────────────────────────────────────────────
347
422
  async function buildCommand(options = {}) {
348
423
  const projectDir = process.cwd();
@@ -400,6 +475,39 @@ async function buildCommand(options = {}) {
400
475
  }
401
476
  }
402
477
 
478
+ // ── Clean up stale mock adapter files ──────────────────────────────────────
479
+ // Mock mode always generates adapters under kafkaMessageBroker/ regardless of
480
+ // the installed broker. When the real broker is RabbitMQ, those files would
481
+ // coexist with the real rabbitmqMessageBroker/ adapters after restore,
482
+ // causing a ConflictingBeanDefinitionException. Remove them proactively.
483
+ if (await configManager.featureExists('rabbitmq')) {
484
+ const { packageName: pkgCleanup } = projectConfig;
485
+ const pkgPathCleanup = toPackagePath(pkgCleanup);
486
+ const modules = projectConfig.modules || [];
487
+ for (const mod of modules) {
488
+ const mockAdapterDir = path.join(
489
+ projectDir, 'src', 'main', 'java', pkgPathCleanup,
490
+ mod, 'infrastructure', 'adapters', 'kafkaMessageBroker'
491
+ );
492
+ if (await fs.pathExists(mockAdapterDir)) {
493
+ // Safety check: only remove if the files are mock implementations
494
+ const files = await fs.readdir(mockAdapterDir);
495
+ const isMockDir = files.length > 0 && await (async () => {
496
+ for (const f of files) {
497
+ const content = await fs.readFile(path.join(mockAdapterDir, f), 'utf-8');
498
+ if (content.includes('ApplicationEventPublisher') && !content.includes('KafkaTemplate')) {
499
+ return true;
500
+ }
501
+ }
502
+ return false;
503
+ })();
504
+ if (isMockDir) {
505
+ await fs.remove(mockAdapterDir);
506
+ }
507
+ }
508
+ }
509
+ }
510
+
403
511
  console.log(chalk.green(' ✅ Configuration restored to original.\n'));
404
512
  }
405
513
 
@@ -423,18 +531,24 @@ async function buildCommand(options = {}) {
423
531
 
424
532
  const backups = await swapBrokerOnly(projectDir, pkgName, configManager);
425
533
  const hasKafkaBackup = !!backups.kafkaConfig;
534
+ const hasRabbitBackup = !!backups.rabbitConfig;
535
+ const hasBrokerBackup = hasKafkaBackup || hasRabbitBackup;
426
536
  console.log(chalk.green(` ✅ ${Object.keys(backups).length} file(s) backed up and replaced`));
427
537
  if (hasKafkaBackup) {
428
538
  console.log(chalk.green(' ✅ KafkaConfig.java removed (Spring Events will be used instead)'));
429
539
  console.log(chalk.green(' ✅ spring-kafka dependencies removed from build.gradle'));
430
540
  }
541
+ if (hasRabbitBackup) {
542
+ console.log(chalk.green(' ✅ RabbitMQConfig.java removed (Spring Events will be used instead)'));
543
+ console.log(chalk.green(' ✅ spring-amqp dependencies removed from build.gradle'));
544
+ }
431
545
  console.log(chalk.gray(' Backup saved to .eva4j.json — will be restored on next eva build\n'));
432
546
 
433
- // ── Regenerate broker layer if system.yaml exists and Kafka was installed
547
+ // ── Regenerate broker layer if system.yaml exists and broker was installed
434
548
  const systemDirBo = path.join(projectDir, 'system');
435
549
  const systemYamlPathBo = path.join(systemDirBo, 'system.yaml');
436
550
 
437
- if (hasKafkaBackup && (await fs.pathExists(systemYamlPathBo))) {
551
+ if (hasBrokerBackup && (await fs.pathExists(systemYamlPathBo))) {
438
552
  let systemConfigBo;
439
553
  try {
440
554
  const content = await fs.readFile(systemYamlPathBo, 'utf-8');
@@ -475,7 +589,7 @@ async function buildCommand(options = {}) {
475
589
  await generateEntitiesCommand(mod.name, { force: false, brokerMode: 'mock' });
476
590
  }
477
591
  }
478
- } else if (hasKafkaBackup) {
592
+ } else if (hasBrokerBackup) {
479
593
  console.log(chalk.yellow(' ℹ️ No system/system.yaml found — broker files must be regenerated manually.'));
480
594
  console.log(chalk.yellow(' Run: eva g entities <module> (with --force if needed)'));
481
595
  }
@@ -495,18 +609,24 @@ async function buildCommand(options = {}) {
495
609
 
496
610
  const backups = await swapToH2(projectDir, pkgName, configManager);
497
611
  const hasKafkaBackup = !!backups.kafkaConfig;
612
+ const hasRabbitBackup = !!backups.rabbitConfig;
613
+ const hasBrokerBackup = hasKafkaBackup || hasRabbitBackup;
498
614
  console.log(chalk.green(` ✅ ${Object.keys(backups).length} file(s) backed up and replaced`));
499
615
  if (hasKafkaBackup) {
500
616
  console.log(chalk.green(' ✅ KafkaConfig.java removed (Spring Events will be used instead)'));
501
617
  console.log(chalk.green(' ✅ spring-kafka dependencies removed from build.gradle'));
502
618
  }
619
+ if (hasRabbitBackup) {
620
+ console.log(chalk.green(' ✅ RabbitMQConfig.java removed (Spring Events will be used instead)'));
621
+ console.log(chalk.green(' ✅ spring-amqp dependencies removed from build.gradle'));
622
+ }
503
623
  console.log(chalk.gray(' Backup saved to .eva4j.json — will be restored on next eva build\n'));
504
624
 
505
- // ── Regenerate broker layer if system.yaml exists and Kafka was installed ──
625
+ // ── Regenerate broker layer if system.yaml exists and broker was installed ──
506
626
  const systemDir = path.join(projectDir, 'system');
507
627
  const systemYamlPath = path.join(systemDir, 'system.yaml');
508
628
 
509
- if (hasKafkaBackup && (await fs.pathExists(systemYamlPath))) {
629
+ if (hasBrokerBackup && (await fs.pathExists(systemYamlPath))) {
510
630
  let systemConfig;
511
631
  try {
512
632
  const content = await fs.readFile(systemYamlPath, 'utf-8');
@@ -549,7 +669,7 @@ async function buildCommand(options = {}) {
549
669
  await generateEntitiesCommand(mod.name, { force: false, brokerMode: 'mock' });
550
670
  }
551
671
  }
552
- } else if (hasKafkaBackup) {
672
+ } else if (hasBrokerBackup) {
553
673
  console.log(chalk.yellow(' ℹ️ No system/system.yaml found — broker files must be regenerated manually.'));
554
674
  console.log(chalk.yellow(' Run: eva g entities <module> (with --force if needed)'));
555
675
  }
@@ -583,6 +703,8 @@ async function buildCommand(options = {}) {
583
703
  }
584
704
 
585
705
  const { modules = [], messaging } = systemConfig;
706
+ const orchestration = systemConfig.orchestration;
707
+ const temporalEnabled = orchestration && orchestration.enabled === true && orchestration.engine === 'temporal';
586
708
 
587
709
  if (!modules.length) {
588
710
  console.log(chalk.yellow('⚠️ No modules defined in system/system.yaml'));
@@ -592,6 +714,9 @@ async function buildCommand(options = {}) {
592
714
  console.log(chalk.blue('\n🏗️ eva build\n'));
593
715
  console.log(chalk.gray(` Project : ${projectConfig.projectName || projectConfig.artifactId}`));
594
716
  console.log(chalk.gray(` Modules : ${modules.map(m => m.name).join(', ')}`));
717
+ if (temporalEnabled) {
718
+ console.log(chalk.gray(` Orchestr: Temporal (${orchestration.temporal && orchestration.temporal.namespace || 'default'})`));
719
+ }
595
720
  console.log();
596
721
 
597
722
  // ── STEP 1: Create modules ───────────────────────────────────────────────
@@ -633,12 +758,34 @@ async function buildCommand(options = {}) {
633
758
  console.log(chalk.cyan(' ➕ Installing kafka-client'));
634
759
  await addKafkaClientCommand();
635
760
  }
761
+ } else if (broker === 'rabbitmq') {
762
+ if (await configManager.featureExists('rabbitmq')) {
763
+ console.log(chalk.gray(' ⏭ rabbitmq-client — already installed, skipping'));
764
+ } else {
765
+ console.log(chalk.cyan(' ➕ Installing rabbitmq-client'));
766
+ await addRabbitMQClientCommand();
767
+ }
636
768
  } else {
637
- console.log(chalk.yellow(` ⚠️ Broker '${broker}' is not supported by eva build (only kafka is supported)`));
769
+ console.log(chalk.yellow(` ⚠️ Broker '${broker}' is not supported by eva build (only kafka and rabbitmq are supported)`));
638
770
  }
639
771
 
640
772
  console.log();
641
773
 
774
+ // ── STEP 2b: Install Temporal client (if orchestration configured) ──────
775
+ if (temporalEnabled) {
776
+ console.log(chalk.blue('━━━ Step 2b: Installing Temporal client ━━━━━━━━━━━━━━━━━━━━━━'));
777
+
778
+ if (await configManager.featureExists('temporal')) {
779
+ console.log(chalk.gray(' ⏭ temporal-client — already installed, skipping'));
780
+ } else {
781
+ console.log(chalk.cyan(' ➕ Installing temporal-client'));
782
+ await addTemporalClientCommand();
783
+ await configManager.loadProjectConfig();
784
+ }
785
+
786
+ console.log();
787
+ }
788
+
642
789
  // ── STEP 3: Copy domain.yaml files ──────────────────────────────────────
643
790
  console.log(chalk.blue('━━━ Step 3: Copying domain.yaml files ━━━━━━━━━━━━━━━━━━━━━━━'));
644
791
 
@@ -684,6 +831,115 @@ async function buildCommand(options = {}) {
684
831
 
685
832
  console.log();
686
833
 
834
+ // ── STEP 4b+4c: Generate Temporal infrastructure & workflows ─────────────
835
+ if (temporalEnabled && (await configManager.featureExists('temporal'))) {
836
+ const rawWorkflows = Array.isArray(systemConfig.workflows) ? systemConfig.workflows : [];
837
+
838
+ // Collect all modules that participate in Temporal (trigger or target)
839
+ const temporalModulesSet = new Set();
840
+ for (const wf of rawWorkflows) {
841
+ if (wf.trigger && wf.trigger.module) temporalModulesSet.add(toCamelCase(wf.trigger.module));
842
+ for (const step of wf.steps || []) {
843
+ if (step.target) temporalModulesSet.add(toCamelCase(step.target));
844
+ }
845
+ }
846
+ // Also include modules with activities: in domain.yaml
847
+ for (const mod of modules) {
848
+ const modCamel = toCamelCase(mod.name);
849
+ const domYaml = path.join(projectDir, 'src', 'main', 'java', packagePath, modCamel, 'domain.yaml');
850
+ if (await fs.pathExists(domYaml)) {
851
+ const data = yaml.load(await fs.readFile(domYaml, 'utf-8'));
852
+ if (data && Array.isArray(data.activities) && data.activities.length > 0) {
853
+ temporalModulesSet.add(modCamel);
854
+ }
855
+ }
856
+ }
857
+
858
+ if (temporalModulesSet.size > 0) {
859
+ // ── Step 4b: Module-level Temporal infrastructure ────────────────────
860
+ console.log(chalk.blue('━━━ Step 4b: Generating Temporal module infrastructure ━━━━━━━'));
861
+
862
+ const temporalFlowTemplates = path.join(__dirname, '..', '..', 'templates', 'temporal-flow');
863
+
864
+ for (const modCamel of temporalModulesSet) {
865
+ const modPascal = toPascalCase(modCamel);
866
+ const modScreamingSnake = toScreamingSnakeCase(modCamel);
867
+ const moduleBasePath = path.join(projectDir, 'src', 'main', 'java', packagePath, modCamel);
868
+ if (!(await fs.pathExists(moduleBasePath))) continue;
869
+
870
+ const checksumMgr = new ChecksumManager(moduleBasePath);
871
+ await checksumMgr.load();
872
+ const wOpts = { force: generateOptions.force, checksumManager: checksumMgr };
873
+
874
+ const ctx = {
875
+ packageName,
876
+ moduleName: modCamel,
877
+ modulePascalCase: modPascal,
878
+ moduleCamelCase: modCamel,
879
+ moduleScreamingSnake: modScreamingSnake,
880
+ };
881
+
882
+ // Marker interfaces (idempotent)
883
+ const interfacesDir = path.join(moduleBasePath, 'domain', 'interfaces');
884
+ await renderAndWrite(
885
+ path.join(temporalFlowTemplates, 'ModuleHeavyActivity.java.ejs'),
886
+ path.join(interfacesDir, `${modPascal}HeavyActivity.java`),
887
+ ctx, wOpts
888
+ );
889
+ await renderAndWrite(
890
+ path.join(temporalFlowTemplates, 'ModuleLightActivity.java.ejs'),
891
+ path.join(interfacesDir, `${modPascal}LightActivity.java`),
892
+ ctx, wOpts
893
+ );
894
+
895
+ // Worker config (idempotent)
896
+ const configDir = path.join(moduleBasePath, 'infrastructure', 'configurations');
897
+ await renderAndWrite(
898
+ path.join(temporalFlowTemplates, 'ModuleTemporalWorkerConfig.java.ejs'),
899
+ path.join(configDir, `${modPascal}TemporalWorkerConfig.java`),
900
+ ctx, wOpts
901
+ );
902
+
903
+ // temporal.yaml queues
904
+ await appendTemporalModuleQueues(projectDir, modCamel, modScreamingSnake);
905
+
906
+ await checksumMgr.save();
907
+ console.log(chalk.green(` ✅ ${modCamel} — Temporal worker infrastructure`));
908
+ }
909
+
910
+ // Generate activities per module (YAML-driven, non-interactive)
911
+ for (const mod of modules) {
912
+ const modCamel = toCamelCase(mod.name);
913
+ if (!temporalModulesSet.has(modCamel)) continue;
914
+
915
+ const domYaml = path.join(projectDir, 'src', 'main', 'java', packagePath, modCamel, 'domain.yaml');
916
+ if (!(await fs.pathExists(domYaml))) continue;
917
+ const data = yaml.load(await fs.readFile(domYaml, 'utf-8'));
918
+ if (!data || !Array.isArray(data.activities) || data.activities.length === 0) continue;
919
+
920
+ console.log(chalk.cyan(` ⚙️ Generating activities for: ${mod.name}`));
921
+ try {
922
+ await generateTemporalActivityCommand(modCamel, null, { force: generateOptions.force, generateAll: true });
923
+ } catch (err) {
924
+ console.log(chalk.yellow(` ⚠️ Activity generation failed for ${mod.name}: ${err.message}`));
925
+ }
926
+ }
927
+
928
+ console.log();
929
+ }
930
+
931
+ // ── Step 4c: Cross-module workflows from system.yaml ──────────────────
932
+ if (rawWorkflows.length > 0) {
933
+ console.log(chalk.blue('━━━ Step 4c: Generating Temporal workflows ━━━━━━━━━━━━━━━━━━'));
934
+ try {
935
+ await generateTemporalSystemCommand({ force: generateOptions.force });
936
+ } catch (err) {
937
+ console.log(chalk.yellow(` ⚠️ Temporal workflow generation failed: ${err.message}`));
938
+ }
939
+ console.log();
940
+ }
941
+ }
942
+
687
943
  // ── STEP 5: Generate unified Postman collection ─────────────────────────
688
944
  console.log(chalk.blue('━━━ Step 5: Generating unified Postman collection ━━━━━━━━━━━━'));
689
945