eva4j 1.0.14 → 1.0.16
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/.agents/skills/skill-creator/LICENSE.txt +202 -0
- package/.agents/skills/skill-creator/SKILL.md +485 -0
- package/.agents/skills/skill-creator/agents/analyzer.md +274 -0
- package/.agents/skills/skill-creator/agents/comparator.md +202 -0
- package/.agents/skills/skill-creator/agents/grader.md +223 -0
- package/.agents/skills/skill-creator/assets/eval_review.html +146 -0
- package/.agents/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.agents/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.agents/skills/skill-creator/references/schemas.md +430 -0
- package/.agents/skills/skill-creator/scripts/__init__.py +0 -0
- package/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.agents/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.agents/skills/skill-creator/scripts/improve_description.py +247 -0
- package/.agents/skills/skill-creator/scripts/package_skill.py +136 -0
- package/.agents/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/.agents/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.agents/skills/skill-creator/scripts/run_loop.py +328 -0
- package/.agents/skills/skill-creator/scripts/utils.py +47 -0
- package/AGENTS.md +268 -6
- package/COMMAND_EVALUATION.md +15 -16
- package/DOMAIN_YAML_GUIDE.md +430 -14
- package/FUTURE_FEATURES.md +1627 -1168
- package/README.md +461 -13
- package/bin/eva4j.js +32 -14
- package/config/defaults.json +1 -0
- package/docs/commands/EVALUATE_SYSTEM.md +746 -261
- package/docs/commands/EXPORT_DIAGRAM.md +153 -0
- package/docs/commands/GENERATE_ENTITIES.md +599 -6
- package/docs/commands/INDEX.md +7 -0
- package/examples/domain-events.yaml +166 -20
- package/examples/domain-listeners.yaml +212 -0
- package/examples/domain-one-to-many.yaml +1 -0
- package/examples/domain-one-to-one.yaml +1 -0
- package/examples/domain-ports.yaml +414 -0
- package/examples/domain-soft-delete.yaml +47 -44
- package/examples/system/notification.yaml +147 -0
- package/examples/system/product.yaml +185 -0
- package/examples/system/system.yaml +112 -0
- package/examples/system-report.html +971 -0
- package/examples/system.yaml +46 -3
- package/package.json +2 -1
- package/src/agents/design-reviewer.agent.md +163 -0
- package/src/commands/build.js +714 -0
- package/src/commands/create.js +1 -0
- package/src/commands/detach.js +149 -108
- package/src/commands/evaluate-system.js +234 -8
- package/src/commands/export-diagram.js +522 -0
- package/src/commands/generate-entities.js +685 -66
- package/src/commands/generate-http-exchange.js +2 -0
- package/src/commands/generate-kafka-event.js +43 -10
- package/src/generators/base-generator.js +18 -6
- package/src/generators/postman-generator.js +188 -0
- package/src/generators/shared-generator.js +12 -2
- package/src/skills/build-system-yaml/SKILL.md +323 -0
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +410 -0
- package/src/skills/build-system-yaml/references/module-spec.md +367 -0
- package/src/skills/build-system-yaml/references/system-yaml-spec.md +178 -0
- package/src/utils/config-manager.js +54 -0
- package/src/utils/context-builder.js +1 -0
- package/src/utils/domain-diagram.js +192 -0
- package/src/utils/domain-validator.js +1020 -0
- package/src/utils/fake-data.js +376 -0
- package/src/utils/system-validator.js +319 -199
- package/src/utils/yaml-to-entity.js +272 -7
- package/templates/aggregate/AggregateMapper.java.ejs +3 -2
- package/templates/aggregate/AggregateRepository.java.ejs +3 -2
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +6 -5
- package/templates/aggregate/AggregateRoot.java.ejs +60 -2
- package/templates/aggregate/DomainEventHandler.java.ejs +4 -1
- package/templates/aggregate/DomainEventRecord.java.ejs +24 -8
- package/templates/aggregate/DomainEventSnapshot.java.ejs +46 -0
- package/templates/aggregate/JpaAggregateRoot.java.ejs +6 -0
- package/templates/base/docker/Dockerfile.ejs +21 -0
- package/templates/base/gradle/build.gradle.ejs +3 -2
- package/templates/base/root/AGENTS.md.ejs +306 -45
- package/templates/crud/ApplicationMapper.java.ejs +4 -0
- package/templates/crud/Controller.java.ejs +4 -4
- package/templates/crud/CreateCommand.java.ejs +4 -0
- package/templates/crud/CreateCommandHandler.java.ejs +3 -6
- package/templates/crud/CreateItemDto.java.ejs +4 -0
- package/templates/crud/CreateValueObjectDto.java.ejs +4 -0
- package/templates/crud/DeleteCommandHandler.java.ejs +12 -6
- package/templates/crud/EndpointsController.java.ejs +6 -6
- package/templates/crud/FindByQuery.java.ejs +1 -1
- package/templates/crud/FindByQueryHandler.java.ejs +3 -9
- package/templates/crud/GetQueryHandler.java.ejs +2 -5
- package/templates/crud/ListQuery.java.ejs +1 -1
- package/templates/crud/ListQueryHandler.java.ejs +8 -14
- package/templates/crud/ScaffoldCommandHandler.java.ejs +2 -4
- package/templates/crud/ScaffoldQuery.java.ejs +3 -2
- package/templates/crud/ScaffoldQueryHandler.java.ejs +5 -6
- package/templates/crud/SubEntityAddCommand.java.ejs +4 -0
- package/templates/crud/SubEntityAddCommandHandler.java.ejs +2 -6
- package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +2 -7
- package/templates/crud/TransitionCommandHandler.java.ejs +2 -6
- package/templates/crud/UpdateCommand.java.ejs +4 -0
- package/templates/crud/UpdateCommandHandler.java.ejs +2 -5
- package/templates/evaluate/report.html.ejs +394 -2
- package/templates/kafka-event/DomainEventHandlerMethod.ejs +3 -1
- package/templates/kafka-event/Event.java.ejs +9 -0
- package/templates/kafka-listener/KafkaController.java.ejs +1 -1
- package/templates/kafka-listener/KafkaListenerClass.java.ejs +1 -1
- package/templates/kafka-listener/ListenerClass.java.ejs +65 -0
- package/templates/kafka-listener/ListenerCommand.java.ejs +31 -0
- package/templates/kafka-listener/ListenerCommandHandler.java.ejs +25 -0
- package/templates/kafka-listener/ListenerIntegrationEvent.java.ejs +37 -0
- package/templates/kafka-listener/ListenerMethod.java.ejs +1 -1
- package/templates/kafka-listener/ListenerNestedType.java.ejs +28 -0
- package/templates/mock/MockEvent.java.ejs +10 -0
- package/templates/mock/MockMessageBrokerImpl.java.ejs +35 -0
- package/templates/mock/MockMessageBrokerImplMethod.java.ejs +6 -0
- package/templates/mock/SpringEventListener.java.ejs +61 -0
- package/templates/ports/PortDomainModel.java.ejs +35 -0
- package/templates/ports/PortFeignAdapter.java.ejs +67 -0
- package/templates/ports/PortFeignClient.java.ejs +45 -0
- package/templates/ports/PortFeignConfig.java.ejs +24 -0
- package/templates/ports/PortInterface.java.ejs +45 -0
- package/templates/ports/PortNestedType.java.ejs +28 -0
- package/templates/ports/PortRequestDto.java.ejs +30 -0
- package/templates/ports/PortResponseDto.java.ejs +28 -0
- package/templates/postman/Collection.json.ejs +1 -1
- package/templates/postman/UnifiedCollection.json.ejs +185 -0
- package/templates/shared/annotations/LogAfter.java.ejs +2 -0
- package/templates/shared/annotations/LogBefore.java.ejs +2 -0
- package/templates/shared/annotations/LogExceptions.java.ejs +2 -0
- package/templates/shared/annotations/LogLevel.java.ejs +8 -0
- package/templates/shared/annotations/LogTimer.java.ejs +1 -0
- package/templates/shared/annotations/Loggable.java.ejs +17 -0
- package/templates/shared/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs +109 -0
- package/templates/shared/configurations/loggerConfig/HandlerLogs.java.ejs +120 -32
- package/templates/shared/errorMessage/ErrorResponse.java.ejs +23 -0
- package/templates/shared/handlerException/HandlerExceptions.java.ejs +99 -79
- package/src/commands/generate-system.js +0 -243
- package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +0 -1103
- package/templates/base/root/skill-build-domain-yaml.ejs +0 -292
- package/templates/base/root/skill-build-system-yaml.ejs +0 -252
- package/templates/shared/errorMessage/ErrorMessage.java.ejs +0 -5
- package/templates/shared/errorMessage/FullErrorMessage.java.ejs +0 -9
- package/templates/shared/errorMessage/ShortErrorMessage.java.ejs +0 -6
|
@@ -166,9 +166,9 @@ async function generateKafkaEventCommand(moduleName, eventName) {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
const topicNameKebab = toKebabCase(name);
|
|
170
|
-
const topicNameCamel = toCamelCase(name);
|
|
171
|
-
const topicNameSnake = toSnakeCase(name).toUpperCase();
|
|
169
|
+
const topicNameKebab = toKebabCase(stripEventSuffix(name));
|
|
170
|
+
const topicNameCamel = toCamelCase(stripEventSuffix(name));
|
|
171
|
+
const topicNameSnake = toSnakeCase(stripEventSuffix(name)).toUpperCase();
|
|
172
172
|
const topicSpringProperty = `\${topics.${topicNameKebab}}`;
|
|
173
173
|
const selectedDomainEvent = domainEventMap[normalizedName] || null;
|
|
174
174
|
|
|
@@ -218,8 +218,8 @@ async function generateKafkaEventCommand(moduleName, eventName) {
|
|
|
218
218
|
|
|
219
219
|
if (!isBatch && generated.length === 1) {
|
|
220
220
|
const r = generated[0];
|
|
221
|
-
const topicSnake = toSnakeCase(eventNames[0]).toUpperCase();
|
|
222
|
-
const topicKebab = toKebabCase(eventNames[0]);
|
|
221
|
+
const topicSnake = toSnakeCase(stripEventSuffix(eventNames[0])).toUpperCase();
|
|
222
|
+
const topicKebab = toKebabCase(stripEventSuffix(eventNames[0]));
|
|
223
223
|
console.log(chalk.blue('\n✅ Kafka event configured successfully!'));
|
|
224
224
|
console.log(chalk.white(`\n Event: ${r.name}`));
|
|
225
225
|
console.log(chalk.white(` Topic: ${topicSnake} (${topicKebab})`));
|
|
@@ -393,6 +393,15 @@ async function createOrUpdateKafkaMessageBroker(projectDir, packagePath, context
|
|
|
393
393
|
// Update existing implementation
|
|
394
394
|
let content = await fs.readFile(adapterPath, 'utf-8');
|
|
395
395
|
|
|
396
|
+
// If this is a mock impl (generated by build --mock), replace it wholesale
|
|
397
|
+
// with the real Kafka implementation before proceeding.
|
|
398
|
+
const isMockImpl = content.includes('ApplicationEventPublisher') && !content.includes('KafkaTemplate');
|
|
399
|
+
if (isMockImpl) {
|
|
400
|
+
const templatePath = path.join(__dirname, '..', '..', 'templates', 'kafka-event', 'KafkaMessageBroker.java.ejs');
|
|
401
|
+
await renderAndWrite(templatePath, adapterPath, context);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
396
405
|
// Check if method already exists
|
|
397
406
|
if (content.includes(methodName)) {
|
|
398
407
|
return; // Method already exists
|
|
@@ -606,8 +615,10 @@ async function updateDomainEventHandler(projectDir, packagePath, context) {
|
|
|
606
615
|
}
|
|
607
616
|
|
|
608
617
|
// 4. Render the mapping call and replace the TODO block
|
|
618
|
+
// Derive aggregateName from the handler file name (e.g. BikeDomainEventHandler.java → Bike)
|
|
619
|
+
const aggregateName = handlerFile.replace('DomainEventHandler.java', '');
|
|
609
620
|
const templatePath = path.join(__dirname, '..', '..', 'templates', 'kafka-event', 'DomainEventHandlerMethod.ejs');
|
|
610
|
-
const mappingLine = await renderTemplate(templatePath, { ...context, domainEventFields: context.eventFields });
|
|
621
|
+
const mappingLine = await renderTemplate(templatePath, { ...context, domainEventFields: context.eventFields, aggregateName });
|
|
611
622
|
|
|
612
623
|
const todoRegex = new RegExp(
|
|
613
624
|
`([ \\t]*\/\/ TODO: handle ${domainEventName}[^\\n]*\\n)(?:[ \\t]*\/\/[^\\n]*\\n)*`
|
|
@@ -651,6 +662,18 @@ async function getInstalledBroker(configManager) {
|
|
|
651
662
|
return null;
|
|
652
663
|
}
|
|
653
664
|
|
|
665
|
+
/**
|
|
666
|
+
* Strip the conventional Java 'Event' suffix from an event class name
|
|
667
|
+
* before deriving the Kafka topic name.
|
|
668
|
+
* ProductPublishedEvent → ProductPublished → PRODUCT_PUBLISHED
|
|
669
|
+
*
|
|
670
|
+
* If the event has an explicit `topic:` property in domain.yaml, that takes
|
|
671
|
+
* precedence and this function is not called.
|
|
672
|
+
*/
|
|
673
|
+
function stripEventSuffix(name) {
|
|
674
|
+
return name.endsWith('Event') ? name.slice(0, -'Event'.length) : name;
|
|
675
|
+
}
|
|
676
|
+
|
|
654
677
|
/**
|
|
655
678
|
* Build a Kafka event generation context for a given domain event.
|
|
656
679
|
* Intended for use by `generate-entities.js` to auto-wire broker integration events
|
|
@@ -658,7 +681,7 @@ async function getInstalledBroker(configManager) {
|
|
|
658
681
|
*
|
|
659
682
|
* @param {string} packageName
|
|
660
683
|
* @param {string} moduleName
|
|
661
|
-
* @param {{ name: string, fields: Array }} domainEvent - Domain event from parsed domain.yaml
|
|
684
|
+
* @param {{ name: string, topic?: string, fields: Array }} domainEvent - Domain event from parsed domain.yaml
|
|
662
685
|
* @param {{ partitions?: number, replicas?: number }} [options]
|
|
663
686
|
* @returns {Object} context ready for generateSingleKafkaEvent()
|
|
664
687
|
*/
|
|
@@ -667,9 +690,15 @@ function buildKafkaEventContext(packageName, moduleName, domainEvent, { partitio
|
|
|
667
690
|
const integrationEventClassName = normalizedName.endsWith('IntegrationEvent')
|
|
668
691
|
? normalizedName
|
|
669
692
|
: `${normalizedName}IntegrationEvent`;
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
693
|
+
// If an explicit topic is declared in domain.yaml, use it as source of truth.
|
|
694
|
+
// Otherwise strip the 'Event' suffix before deriving snake/kebab names so that
|
|
695
|
+
// ProductPublishedEvent → PRODUCT_PUBLISHED (not PRODUCT_PUBLISHED_EVENT).
|
|
696
|
+
const topicBase = domainEvent.topic
|
|
697
|
+
? domainEvent.topic.trim().toUpperCase().replace(/-/g, '_')
|
|
698
|
+
: toSnakeCase(stripEventSuffix(domainEvent.name)).toUpperCase();
|
|
699
|
+
const topicNameSnake = topicBase;
|
|
700
|
+
const topicNameKebab = topicBase.toLowerCase().replace(/_/g, '-');
|
|
701
|
+
const topicNameCamel = toCamelCase(topicNameKebab);
|
|
673
702
|
const topicSpringProperty = `\${topics.${topicNameKebab}}`;
|
|
674
703
|
return {
|
|
675
704
|
packageName,
|
|
@@ -694,3 +723,7 @@ module.exports = generateKafkaEventCommand;
|
|
|
694
723
|
module.exports.generateSingleKafkaEvent = generateSingleKafkaEvent;
|
|
695
724
|
module.exports.buildKafkaEventContext = buildKafkaEventContext;
|
|
696
725
|
module.exports.getInstalledBroker = getInstalledBroker;
|
|
726
|
+
module.exports.updateKafkaYml = updateKafkaYml;
|
|
727
|
+
module.exports.generateEventRecord = generateEventRecord;
|
|
728
|
+
module.exports.createOrUpdateMessageBroker = createOrUpdateMessageBroker;
|
|
729
|
+
module.exports.updateDomainEventHandler = updateDomainEventHandler;
|
|
@@ -63,6 +63,8 @@ class BaseGenerator {
|
|
|
63
63
|
await fs.ensureDir(resources);
|
|
64
64
|
await fs.ensureDir(path.join(resources, 'static'));
|
|
65
65
|
await fs.ensureDir(path.join(resources, 'templates'));
|
|
66
|
+
await fs.ensureDir(path.join(this.projectDir, 'system'));
|
|
67
|
+
await fs.ensureDir(path.join(this.projectDir, '.agents'));
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
async generateApplication() {
|
|
@@ -132,16 +134,14 @@ class BaseGenerator {
|
|
|
132
134
|
path.join(this.projectDir, 'AGENTS.md'));
|
|
133
135
|
await this.generateFile('root/system.yaml.ejs',
|
|
134
136
|
path.join(this.projectDir, 'system.yaml'));
|
|
135
|
-
await this.
|
|
136
|
-
|
|
137
|
-
await this.generateFile('root/skill-build-domain-yaml.ejs',
|
|
138
|
-
path.join(this.projectDir, '.agents', 'skills', 'build-domain-yaml', 'SKILL.md'));
|
|
139
|
-
await this.generateFile('root/skill-build-domain-yaml-references-generate-entities.md.ejs',
|
|
140
|
-
path.join(this.projectDir, '.agents', 'skills', 'build-domain-yaml', 'references', 'GENERATE_ENTITIES.md'));
|
|
137
|
+
await this.copySkills();
|
|
138
|
+
await this.copyAgents();
|
|
141
139
|
|
|
142
140
|
if (this.context.features.includeDocker) {
|
|
143
141
|
await this.generateFile('docker/docker-compose.yaml.ejs',
|
|
144
142
|
path.join(this.projectDir, 'docker-compose.yaml'));
|
|
143
|
+
await this.generateFile('docker/Dockerfile.ejs',
|
|
144
|
+
path.join(this.projectDir, 'Dockerfile'));
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -152,6 +152,18 @@ class BaseGenerator {
|
|
|
152
152
|
`${this.context.applicationClassName}Tests.java`));
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
async copySkills() {
|
|
156
|
+
const srcSkillsDir = path.join(__dirname, '../skills');
|
|
157
|
+
const destSkillsDir = path.join(this.projectDir, '.agents', 'skills');
|
|
158
|
+
await fs.copy(srcSkillsDir, destSkillsDir);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async copyAgents() {
|
|
162
|
+
const srcAgentsDir = path.join(__dirname, '../agents');
|
|
163
|
+
const destAgentsDir = path.join(this.projectDir, '.github', 'agents');
|
|
164
|
+
await fs.copy(srcAgentsDir, destAgentsDir);
|
|
165
|
+
}
|
|
166
|
+
|
|
155
167
|
async generateFile(templateRelPath, destPath) {
|
|
156
168
|
const templatePath = path.join(this.templatesDir, templateRelPath);
|
|
157
169
|
await renderAndWrite(templatePath, destPath, this.context);
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
|
|
8
|
+
const { parseDomainYaml } = require('../utils/yaml-to-entity');
|
|
9
|
+
const { renderTemplate } = require('../utils/template-engine');
|
|
10
|
+
const { toKebabCase, toCamelCase, toPackagePath } = require('../utils/naming');
|
|
11
|
+
const { initSeed, generateFakeValue, generateFakeBody, generateFakeId } = require('../utils/fake-data');
|
|
12
|
+
|
|
13
|
+
const TEMPLATES_DIR = path.join(__dirname, '../../templates/postman');
|
|
14
|
+
|
|
15
|
+
// Audit / internal fields excluded from command bodies
|
|
16
|
+
const EXCLUDED_FIELDS = new Set([
|
|
17
|
+
'id', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy', 'deletedAt',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Determine which aggregate an operation belongs to based on use case naming.
|
|
22
|
+
* E.g. "CreateProduct" → "Product", "FindAllCategorys" → "Category".
|
|
23
|
+
*
|
|
24
|
+
* @param {string} useCase - e.g. "CreateProduct"
|
|
25
|
+
* @param {Array} aggregates - parsed aggregates with `.name`
|
|
26
|
+
* @returns {string|null} - aggregate name or null
|
|
27
|
+
*/
|
|
28
|
+
function resolveOwnerAggregate(useCase, aggregates) {
|
|
29
|
+
const ucLower = useCase.toLowerCase();
|
|
30
|
+
// Sort longest name first so "UserProfile" matches before "User"
|
|
31
|
+
const sorted = [...aggregates].sort((a, b) => b.name.length - a.name.length);
|
|
32
|
+
for (const agg of sorted) {
|
|
33
|
+
if (ucLower.includes(agg.name.toLowerCase())) return agg.name;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generate a unified Postman collection covering every module in the system.
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} opts
|
|
42
|
+
* @param {string} opts.projectDir - Absolute path to the project root
|
|
43
|
+
* @param {string} opts.systemDir - Absolute path to the system/ directory
|
|
44
|
+
* @param {string} opts.packageName - Full Java package name (e.g. "com.example.myapp")
|
|
45
|
+
* @param {Object} opts.systemConfig - Parsed system.yaml object
|
|
46
|
+
* @param {Object} opts.projectConfig - Parsed .eva4j.json project config
|
|
47
|
+
* @returns {Promise<string|null>} - Path to the generated file, or null on error
|
|
48
|
+
*/
|
|
49
|
+
async function generateUnifiedPostmanCollection({
|
|
50
|
+
projectDir,
|
|
51
|
+
systemDir,
|
|
52
|
+
packageName,
|
|
53
|
+
systemConfig,
|
|
54
|
+
projectConfig,
|
|
55
|
+
}) {
|
|
56
|
+
const systemName = systemConfig.system?.name || projectConfig.projectName || projectConfig.artifactId || 'eva4j-app';
|
|
57
|
+
const port = projectConfig.server?.port || 8040;
|
|
58
|
+
const modules = systemConfig.modules || [];
|
|
59
|
+
|
|
60
|
+
if (!modules.length) return null;
|
|
61
|
+
|
|
62
|
+
// Seed faker for deterministic output
|
|
63
|
+
initSeed(42);
|
|
64
|
+
|
|
65
|
+
// ── Collect module contexts ───────────────────────────────────────────────
|
|
66
|
+
const moduleContexts = [];
|
|
67
|
+
|
|
68
|
+
for (const mod of modules) {
|
|
69
|
+
const yamlPath = path.join(systemDir, `${mod.name}.yaml`);
|
|
70
|
+
if (!(await fs.pathExists(yamlPath))) continue;
|
|
71
|
+
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = await parseDomainYaml(yamlPath, packageName, mod.name);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.log(chalk.yellow(` ⚠️ Could not parse ${mod.name}.yaml for Postman: ${err.message}`));
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { aggregates, allEnums, endpoints } = parsed;
|
|
81
|
+
const aggregateContexts = [];
|
|
82
|
+
|
|
83
|
+
for (const agg of aggregates) {
|
|
84
|
+
const rootEntity = agg.rootEntity;
|
|
85
|
+
const idField = rootEntity.fields.find(f => f.name === 'id');
|
|
86
|
+
const idType = idField ? idField.javaType : 'String';
|
|
87
|
+
const exampleId = generateFakeId(idType);
|
|
88
|
+
const trackUser = rootEntity.audit?.trackUser === true;
|
|
89
|
+
const valueObjects = agg.valueObjects || [];
|
|
90
|
+
|
|
91
|
+
// Command fields: exclude id, audit, readOnly, deletedAt
|
|
92
|
+
const commandFields = rootEntity.fields.filter(
|
|
93
|
+
f => !EXCLUDED_FIELDS.has(f.name) && !f.readOnly
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Build fake body for create/update
|
|
97
|
+
const defaultBody = generateFakeBody(commandFields, [], allEnums, valueObjects);
|
|
98
|
+
|
|
99
|
+
if (endpoints && endpoints.versions && endpoints.versions.length > 0) {
|
|
100
|
+
// ── Endpoint-driven ───────────────────────────────────────────────
|
|
101
|
+
const operations = [];
|
|
102
|
+
const bodies = {};
|
|
103
|
+
|
|
104
|
+
for (const version of endpoints.versions) {
|
|
105
|
+
for (const op of version.operations) {
|
|
106
|
+
// Classify operation → which aggregate it belongs to
|
|
107
|
+
const owner = resolveOwnerAggregate(op.useCase, aggregates);
|
|
108
|
+
if (owner && owner !== agg.name) continue;
|
|
109
|
+
// If no aggregate could be resolved, assign to the first aggregate
|
|
110
|
+
if (!owner && agg !== aggregates[0]) continue;
|
|
111
|
+
|
|
112
|
+
const basePath = endpoints.basePath || '/';
|
|
113
|
+
operations.push({
|
|
114
|
+
useCase: op.useCase,
|
|
115
|
+
method: op.method,
|
|
116
|
+
path: op.path || '/',
|
|
117
|
+
basePath,
|
|
118
|
+
version: version.version,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Generate body for write operations
|
|
122
|
+
if (op.method === 'POST' || op.method === 'PUT' || op.method === 'PATCH') {
|
|
123
|
+
// Re-seed per operation so bodies vary
|
|
124
|
+
bodies[op.useCase] = generateFakeBody(commandFields, [], allEnums, valueObjects);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
aggregateContexts.push({
|
|
130
|
+
name: agg.name,
|
|
131
|
+
trackUser,
|
|
132
|
+
idType,
|
|
133
|
+
exampleId,
|
|
134
|
+
resourceNameKebab: toKebabCase(agg.name),
|
|
135
|
+
operations,
|
|
136
|
+
defaultCrud: false,
|
|
137
|
+
bodies,
|
|
138
|
+
});
|
|
139
|
+
} else {
|
|
140
|
+
// ── Default CRUD (no endpoints section) ──────────────────────────
|
|
141
|
+
aggregateContexts.push({
|
|
142
|
+
name: agg.name,
|
|
143
|
+
trackUser,
|
|
144
|
+
idType,
|
|
145
|
+
exampleId,
|
|
146
|
+
resourceNameKebab: toKebabCase(agg.name),
|
|
147
|
+
operations: null,
|
|
148
|
+
defaultCrud: true,
|
|
149
|
+
bodies: { default: defaultBody },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (aggregateContexts.length > 0) {
|
|
155
|
+
moduleContexts.push({
|
|
156
|
+
name: mod.name,
|
|
157
|
+
aggregates: aggregateContexts,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!moduleContexts.length) return null;
|
|
163
|
+
|
|
164
|
+
// ── Render template ─────────────────────────────────────────────────────
|
|
165
|
+
const templatePath = path.join(TEMPLATES_DIR, 'UnifiedCollection.json.ejs');
|
|
166
|
+
const collectionId = crypto.randomUUID();
|
|
167
|
+
|
|
168
|
+
const context = {
|
|
169
|
+
systemName,
|
|
170
|
+
collectionId,
|
|
171
|
+
port,
|
|
172
|
+
modules: moduleContexts,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const content = await renderTemplate(templatePath, context);
|
|
176
|
+
|
|
177
|
+
// ── Write output ────────────────────────────────────────────────────────
|
|
178
|
+
const outputDir = path.join(projectDir, 'postman');
|
|
179
|
+
await fs.ensureDir(outputDir);
|
|
180
|
+
|
|
181
|
+
const outputFileName = `${toKebabCase(systemName)}-Postman-Collection.json`;
|
|
182
|
+
const outputPath = path.join(outputDir, outputFileName);
|
|
183
|
+
await fs.writeFile(outputPath, content, 'utf-8');
|
|
184
|
+
|
|
185
|
+
return outputPath;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = { generateUnifiedPostmanCollection };
|
|
@@ -136,7 +136,7 @@ class SharedGenerator {
|
|
|
136
136
|
|
|
137
137
|
async generateAnnotations(basePath) {
|
|
138
138
|
const annotationsPath = path.join(basePath, 'domain', 'annotations');
|
|
139
|
-
const files = ['ApplicationComponent', 'DomainComponent', 'LogAfter', 'LogBefore', 'LogExceptions', 'LogTimer'];
|
|
139
|
+
const files = ['ApplicationComponent', 'DomainComponent', 'Loggable', 'LogAfter', 'LogBefore', 'LogExceptions', 'LogLevel', 'LogTimer'];
|
|
140
140
|
|
|
141
141
|
for (const file of files) {
|
|
142
142
|
await this.generateFile(`annotations/${file}.java.ejs`,
|
|
@@ -170,7 +170,7 @@ class SharedGenerator {
|
|
|
170
170
|
|
|
171
171
|
async generateErrorMessages(basePath) {
|
|
172
172
|
const errorMessagePath = path.join(basePath, 'domain', 'errorMessage');
|
|
173
|
-
const files = ['
|
|
173
|
+
const files = ['ErrorResponse'];
|
|
174
174
|
|
|
175
175
|
for (const file of files) {
|
|
176
176
|
await this.generateFile(`errorMessage/${file}.java.ejs`,
|
|
@@ -194,6 +194,12 @@ class SharedGenerator {
|
|
|
194
194
|
path.join(handlerExceptionPath, 'HandlerExceptions.java'));
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
async generateMockEvent(basePath) {
|
|
198
|
+
const destPath = path.join(basePath, 'infrastructure', 'mockEvent', 'MockEvent.java');
|
|
199
|
+
const templatePath = path.join(__dirname, '../../templates/mock/MockEvent.java.ejs');
|
|
200
|
+
await renderAndWrite(templatePath, destPath, this.context, { overwrite: false });
|
|
201
|
+
}
|
|
202
|
+
|
|
197
203
|
async generateConfigurations(basePath) {
|
|
198
204
|
const configurationsPath = path.join(basePath, 'infrastructure', 'configurations');
|
|
199
205
|
|
|
@@ -209,6 +215,10 @@ class SharedGenerator {
|
|
|
209
215
|
await this.generateFile('configurations/swaggerConfig/SwaggerConfig.java.ejs',
|
|
210
216
|
path.join(configurationsPath, 'swaggerConfig', 'SwaggerConfig.java'));
|
|
211
217
|
|
|
218
|
+
// Event publication schema fix (Spring Modulith varchar(255) → TEXT)
|
|
219
|
+
await this.generateFile('configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs',
|
|
220
|
+
path.join(configurationsPath, 'eventPublicationConfig', 'EventPublicationSchemaConfig.java'));
|
|
221
|
+
|
|
212
222
|
// UseCase config
|
|
213
223
|
await this.generateFile('configurations/useCaseConfig/UseCaseAutoRegister.java.ejs',
|
|
214
224
|
path.join(configurationsPath, 'useCaseConfig', 'UseCaseAutoRegister.java'));
|