eva4j 1.0.16 → 1.0.17
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.md +218 -5
- package/DOMAIN_YAML_GUIDE.md +185 -2
- package/FUTURE_FEATURES.md +33 -52
- package/docs/commands/EVALUATE_SYSTEM.md +18 -2
- package/examples/domain-events.yaml +26 -0
- package/examples/domain-read-models.yaml +113 -0
- package/package.json +1 -1
- package/read-model-spec.md +664 -0
- package/src/agents/design-reviewer.agent.md +3 -0
- package/src/commands/generate-entities.js +254 -10
- package/src/commands/generate-http-exchange.js +3 -0
- package/src/commands/generate-kafka-event.js +3 -0
- package/src/commands/generate-kafka-listener.js +3 -0
- package/src/commands/generate-record.js +2 -2
- package/src/commands/generate-resource.js +4 -1
- package/src/commands/generate-temporal-activity.js +4 -1
- package/src/commands/generate-temporal-flow.js +4 -1
- package/src/commands/generate-usecase.js +4 -1
- package/src/skills/build-system-yaml/SKILL.md +122 -1
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +205 -24
- package/src/skills/build-system-yaml/references/module-spec.md +33 -1
- package/src/skills/build-system-yaml/references/system-yaml-spec.md +36 -0
- package/src/utils/config-manager.js +4 -2
- package/src/utils/domain-validator.js +230 -14
- package/src/utils/naming.js +10 -0
- package/src/utils/validator.js +3 -1
- package/src/utils/yaml-to-entity.js +272 -3
- package/templates/aggregate/AggregateRepository.java.ejs +4 -0
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +8 -0
- package/templates/aggregate/AggregateRoot.java.ejs +38 -4
- package/templates/aggregate/JpaAggregateRoot.java.ejs +2 -2
- package/templates/crud/DeleteCommandHandler.java.ejs +19 -1
- package/templates/crud/UpdateCommandHandler.java.ejs +53 -2
- package/templates/read-model/ReadModelDomain.java.ejs +46 -0
- package/templates/read-model/ReadModelJpa.java.ejs +58 -0
- package/templates/read-model/ReadModelJpaRepository.java.ejs +11 -0
- package/templates/read-model/ReadModelKafkaListener.java.ejs +64 -0
- package/templates/read-model/ReadModelRepository.java.ejs +42 -0
- package/templates/read-model/ReadModelRepositoryImpl.java.ejs +81 -0
- package/templates/read-model/ReadModelSyncHandler.java.ejs +52 -0
- package/test-c2010.js +49 -0
- package/test-update-compat.js +109 -0
- package/test-update-lifecycle.js +121 -0
|
@@ -5,9 +5,9 @@ const fs = require('fs-extra');
|
|
|
5
5
|
const inquirer = require('inquirer');
|
|
6
6
|
const ConfigManager = require('../utils/config-manager');
|
|
7
7
|
const { isEva4jProject } = require('../utils/validator');
|
|
8
|
-
const { toPackagePath, toCamelCase, toKebabCase, toPascalCase, getApplicationClassName, pluralizeWord } = require('../utils/naming');
|
|
8
|
+
const { toPackagePath, toCamelCase, toKebabCase, toPascalCase, getApplicationClassName, pluralizeWord, singularizeWord } = require('../utils/naming');
|
|
9
9
|
const { renderAndWrite, renderTemplate } = require('../utils/template-engine');
|
|
10
|
-
const { parseDomainYaml, generateEntityImports, generateValidationImports } = require('../utils/yaml-to-entity');
|
|
10
|
+
const { parseDomainYaml, generateEntityImports, generateValidationImports, resolveLifecycleEventArgs, resolveEventArgs } = require('../utils/yaml-to-entity');
|
|
11
11
|
const { createOrUpdateUrlsConfig, ensureUrlsImport } = require('./generate-http-exchange');
|
|
12
12
|
const SharedGenerator = require('../generators/shared-generator');
|
|
13
13
|
const ChecksumManager = require('../utils/checksum-manager');
|
|
@@ -216,6 +216,9 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
216
216
|
const { packageName, artifactId } = projectConfig;
|
|
217
217
|
const packagePath = toPackagePath(packageName);
|
|
218
218
|
|
|
219
|
+
// Normalise module name to camelCase (system.yaml uses kebab-case, .eva4j.json stores camelCase)
|
|
220
|
+
moduleName = toCamelCase(moduleName);
|
|
221
|
+
|
|
219
222
|
// Validate module exists
|
|
220
223
|
if (!(await configManager.moduleExists(moduleName))) {
|
|
221
224
|
console.error(chalk.red(`❌ Module '${moduleName}' not found`));
|
|
@@ -244,7 +247,7 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
244
247
|
|
|
245
248
|
try {
|
|
246
249
|
// Parse domain.yaml
|
|
247
|
-
const { aggregates, allEnums, endpoints, listeners, ports } = await parseDomainYaml(domainYamlPath, packageName, moduleName);
|
|
250
|
+
const { aggregates, allEnums, endpoints, listeners, ports, readModels } = await parseDomainYaml(domainYamlPath, packageName, moduleName);
|
|
248
251
|
|
|
249
252
|
spinner.succeed(chalk.green(`Found ${aggregates.length} aggregate(s) and ${allEnums.length} enum(s)`));
|
|
250
253
|
|
|
@@ -273,7 +276,7 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
273
276
|
}
|
|
274
277
|
|
|
275
278
|
// Detect installed message broker for auto-wiring integration events
|
|
276
|
-
const installedBroker = (hasDomainEventsInModule || (listeners && listeners.length > 0))
|
|
279
|
+
const installedBroker = (hasDomainEventsInModule || (listeners && listeners.length > 0) || (readModels && readModels.length > 0))
|
|
277
280
|
? await getInstalledBroker(configManager)
|
|
278
281
|
: null;
|
|
279
282
|
// When brokerMode:'mock' is requested AND a broker is installed, use mock Spring-Events adapter
|
|
@@ -369,6 +372,9 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
369
372
|
const { name: aggregateName, rootEntity, secondaryEntities, valueObjects } = aggregate;
|
|
370
373
|
|
|
371
374
|
// 1. Generate Domain Aggregate Root
|
|
375
|
+
const resolvedLifecycle = resolveLifecycleEventArgs(
|
|
376
|
+
aggregate.lifecycleEventsMap || {}, rootEntity.name, rootEntity.fields, valueObjects
|
|
377
|
+
);
|
|
372
378
|
const rootDomainContext = {
|
|
373
379
|
packageName,
|
|
374
380
|
moduleName,
|
|
@@ -381,7 +387,8 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
381
387
|
auditable: rootEntity.auditable,
|
|
382
388
|
hasSoftDelete: rootEntity.hasSoftDelete || false,
|
|
383
389
|
domainEvents: aggregate.domainEvents || [],
|
|
384
|
-
triggeredEventsMap: aggregate.triggeredEventsMap || {}
|
|
390
|
+
triggeredEventsMap: aggregate.triggeredEventsMap || {},
|
|
391
|
+
lifecycleEventsMap: resolvedLifecycle
|
|
385
392
|
};
|
|
386
393
|
|
|
387
394
|
await renderAndWrite(
|
|
@@ -393,6 +400,7 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
393
400
|
generatedFiles.push({ type: 'Domain Entity', name: rootEntity.name, path: `${moduleName}/domain/models/entities/${rootEntity.name}.java` });
|
|
394
401
|
|
|
395
402
|
// 2. Generate JPA Aggregate Root
|
|
403
|
+
const hasCreateLifecycle = !!(aggregate.lifecycleEventsMap && aggregate.lifecycleEventsMap.create && aggregate.lifecycleEventsMap.create.length > 0);
|
|
396
404
|
const rootJpaContext = {
|
|
397
405
|
packageName,
|
|
398
406
|
moduleName,
|
|
@@ -405,7 +413,8 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
405
413
|
enums: allEnums,
|
|
406
414
|
auditable: rootEntity.auditable,
|
|
407
415
|
audit: rootEntity.audit,
|
|
408
|
-
hasSoftDelete: rootEntity.hasSoftDelete || false
|
|
416
|
+
hasSoftDelete: rootEntity.hasSoftDelete || false,
|
|
417
|
+
hasCreateLifecycle
|
|
409
418
|
};
|
|
410
419
|
|
|
411
420
|
await renderAndWrite(
|
|
@@ -524,6 +533,8 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
524
533
|
moduleName,
|
|
525
534
|
rootEntity,
|
|
526
535
|
hasSoftDelete: rootEntity.hasSoftDelete || false,
|
|
536
|
+
hasDomainEvents: (aggregate.domainEvents || []).length > 0,
|
|
537
|
+
hasDeleteLifecycle: !!(aggregate.lifecycleEventsMap || {}).delete,
|
|
527
538
|
findByOps: []
|
|
528
539
|
};
|
|
529
540
|
|
|
@@ -551,6 +562,7 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
551
562
|
aggregateName,
|
|
552
563
|
rootEntity,
|
|
553
564
|
hasDomainEvents: (aggregate.domainEvents || []).length > 0,
|
|
565
|
+
hasDeleteLifecycle: !!(aggregate.lifecycleEventsMap || {}).delete,
|
|
554
566
|
hasSoftDelete: rootEntity.hasSoftDelete || false,
|
|
555
567
|
findByOps: []
|
|
556
568
|
};
|
|
@@ -1072,6 +1084,190 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
1072
1084
|
spinner.succeed(chalk.green(`HTTP ports generated! ✨`));
|
|
1073
1085
|
}
|
|
1074
1086
|
|
|
1087
|
+
// ── Generate Read Models (local projections of external data) ────────────
|
|
1088
|
+
if (readModels && readModels.length > 0) {
|
|
1089
|
+
if (broker === 'kafka' || broker === 'mock') {
|
|
1090
|
+
spinner.start(`Generating ${readModels.length} read model(s)...`);
|
|
1091
|
+
|
|
1092
|
+
const readModelTemplatesDir = path.join(__dirname, '..', '..', 'templates', 'read-model');
|
|
1093
|
+
|
|
1094
|
+
for (const rm of readModels) {
|
|
1095
|
+
const rmContext = {
|
|
1096
|
+
packageName,
|
|
1097
|
+
moduleName,
|
|
1098
|
+
...rm
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
// 1. Domain read model class
|
|
1102
|
+
const domainPath = path.join(
|
|
1103
|
+
moduleBasePath, 'domain', 'models', 'readmodels',
|
|
1104
|
+
`${rm.domainClassName}.java`
|
|
1105
|
+
);
|
|
1106
|
+
await renderAndWrite(
|
|
1107
|
+
path.join(readModelTemplatesDir, 'ReadModelDomain.java.ejs'),
|
|
1108
|
+
domainPath,
|
|
1109
|
+
rmContext,
|
|
1110
|
+
writeOptions
|
|
1111
|
+
);
|
|
1112
|
+
generatedFiles.push({
|
|
1113
|
+
type: 'Read Model',
|
|
1114
|
+
name: rm.domainClassName,
|
|
1115
|
+
path: `${moduleName}/domain/models/readmodels/${rm.domainClassName}.java`
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
// 2. JPA entity
|
|
1119
|
+
const jpaPath = path.join(
|
|
1120
|
+
moduleBasePath, 'infrastructure', 'database', 'entities',
|
|
1121
|
+
`${rm.jpaEntityName}.java`
|
|
1122
|
+
);
|
|
1123
|
+
await renderAndWrite(
|
|
1124
|
+
path.join(readModelTemplatesDir, 'ReadModelJpa.java.ejs'),
|
|
1125
|
+
jpaPath,
|
|
1126
|
+
rmContext,
|
|
1127
|
+
writeOptions
|
|
1128
|
+
);
|
|
1129
|
+
generatedFiles.push({
|
|
1130
|
+
type: 'Read Model',
|
|
1131
|
+
name: rm.jpaEntityName,
|
|
1132
|
+
path: `${moduleName}/infrastructure/database/entities/${rm.jpaEntityName}.java`
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
// 3. JPA repository
|
|
1136
|
+
const jpaRepoPath = path.join(
|
|
1137
|
+
moduleBasePath, 'infrastructure', 'database', 'repositories',
|
|
1138
|
+
`${rm.jpaRepositoryName}.java`
|
|
1139
|
+
);
|
|
1140
|
+
await renderAndWrite(
|
|
1141
|
+
path.join(readModelTemplatesDir, 'ReadModelJpaRepository.java.ejs'),
|
|
1142
|
+
jpaRepoPath,
|
|
1143
|
+
rmContext,
|
|
1144
|
+
writeOptions
|
|
1145
|
+
);
|
|
1146
|
+
generatedFiles.push({
|
|
1147
|
+
type: 'Read Model',
|
|
1148
|
+
name: rm.jpaRepositoryName,
|
|
1149
|
+
path: `${moduleName}/infrastructure/database/repositories/${rm.jpaRepositoryName}.java`
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
// 4. Domain repository interface
|
|
1153
|
+
const repoPath = path.join(
|
|
1154
|
+
moduleBasePath, 'domain', 'repositories',
|
|
1155
|
+
`${rm.repositoryName}.java`
|
|
1156
|
+
);
|
|
1157
|
+
await renderAndWrite(
|
|
1158
|
+
path.join(readModelTemplatesDir, 'ReadModelRepository.java.ejs'),
|
|
1159
|
+
repoPath,
|
|
1160
|
+
rmContext,
|
|
1161
|
+
writeOptions
|
|
1162
|
+
);
|
|
1163
|
+
generatedFiles.push({
|
|
1164
|
+
type: 'Read Model',
|
|
1165
|
+
name: rm.repositoryName,
|
|
1166
|
+
path: `${moduleName}/domain/repositories/${rm.repositoryName}.java`
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
// 5. Repository implementation
|
|
1170
|
+
const repoImplPath = path.join(
|
|
1171
|
+
moduleBasePath, 'infrastructure', 'database', 'repositories',
|
|
1172
|
+
`${rm.repositoryImplName}.java`
|
|
1173
|
+
);
|
|
1174
|
+
await renderAndWrite(
|
|
1175
|
+
path.join(readModelTemplatesDir, 'ReadModelRepositoryImpl.java.ejs'),
|
|
1176
|
+
repoImplPath,
|
|
1177
|
+
rmContext,
|
|
1178
|
+
writeOptions
|
|
1179
|
+
);
|
|
1180
|
+
generatedFiles.push({
|
|
1181
|
+
type: 'Read Model',
|
|
1182
|
+
name: rm.repositoryImplName,
|
|
1183
|
+
path: `${moduleName}/infrastructure/database/repositories/${rm.repositoryImplName}.java`
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
// 6. Sync handler
|
|
1187
|
+
const handlerPath = path.join(
|
|
1188
|
+
moduleBasePath, 'application', 'usecases',
|
|
1189
|
+
`${rm.syncHandlerName}.java`
|
|
1190
|
+
);
|
|
1191
|
+
await renderAndWrite(
|
|
1192
|
+
path.join(readModelTemplatesDir, 'ReadModelSyncHandler.java.ejs'),
|
|
1193
|
+
handlerPath,
|
|
1194
|
+
rmContext,
|
|
1195
|
+
writeOptions
|
|
1196
|
+
);
|
|
1197
|
+
generatedFiles.push({
|
|
1198
|
+
type: 'Read Model',
|
|
1199
|
+
name: rm.syncHandlerName,
|
|
1200
|
+
path: `${moduleName}/application/usecases/${rm.syncHandlerName}.java`
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
// 7. Per syncedBy event: integration event + Kafka listener + topic registration
|
|
1204
|
+
for (const sync of rm.syncedBy) {
|
|
1205
|
+
const listenerContext = {
|
|
1206
|
+
packageName,
|
|
1207
|
+
moduleName,
|
|
1208
|
+
...sync,
|
|
1209
|
+
syncHandlerName: rm.syncHandlerName,
|
|
1210
|
+
domainClassName: rm.domainClassName,
|
|
1211
|
+
repositoryName: rm.repositoryName
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1214
|
+
// Integration event (reuse if already exists from listeners: section)
|
|
1215
|
+
// Only generate for UPSERT — DELETE/SOFT_DELETE bypass IntegrationEvent entirely
|
|
1216
|
+
const integrationEventPath = path.join(
|
|
1217
|
+
moduleBasePath, 'application', 'events',
|
|
1218
|
+
`${sync.integrationEventClassName}.java`
|
|
1219
|
+
);
|
|
1220
|
+
if (sync.action === 'UPSERT' && !(await fs.pathExists(integrationEventPath))) {
|
|
1221
|
+
await renderAndWrite(
|
|
1222
|
+
path.join(__dirname, '..', '..', 'templates', 'kafka-listener', 'ListenerIntegrationEvent.java.ejs'),
|
|
1223
|
+
integrationEventPath,
|
|
1224
|
+
{
|
|
1225
|
+
packageName,
|
|
1226
|
+
moduleName,
|
|
1227
|
+
integrationEventClassName: sync.integrationEventClassName,
|
|
1228
|
+
topicConstant: sync.topicConstant,
|
|
1229
|
+
producer: rm.sourceModule,
|
|
1230
|
+
fields: sync.fields
|
|
1231
|
+
},
|
|
1232
|
+
writeOptions
|
|
1233
|
+
);
|
|
1234
|
+
generatedFiles.push({
|
|
1235
|
+
type: 'Read Model Integration Event',
|
|
1236
|
+
name: sync.integrationEventClassName,
|
|
1237
|
+
path: `${moduleName}/application/events/${sync.integrationEventClassName}.java`
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Kafka listener
|
|
1242
|
+
if (broker === 'kafka') {
|
|
1243
|
+
const kafkaListenerPath = path.join(
|
|
1244
|
+
moduleBasePath, 'infrastructure', 'kafkaListener',
|
|
1245
|
+
`${sync.listenerClassName}.java`
|
|
1246
|
+
);
|
|
1247
|
+
await renderAndWrite(
|
|
1248
|
+
path.join(readModelTemplatesDir, 'ReadModelKafkaListener.java.ejs'),
|
|
1249
|
+
kafkaListenerPath,
|
|
1250
|
+
listenerContext,
|
|
1251
|
+
writeOptions
|
|
1252
|
+
);
|
|
1253
|
+
generatedFiles.push({
|
|
1254
|
+
type: 'Read Model Kafka Listener',
|
|
1255
|
+
name: sync.listenerClassName,
|
|
1256
|
+
path: `${moduleName}/infrastructure/kafkaListener/${sync.listenerClassName}.java`
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
// Register topic in kafka.yaml
|
|
1260
|
+
await updateKafkaYml(projectDir, sync.topicKey, sync.topicConstant);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
spinner.succeed(chalk.green(`Read models generated! ✨`));
|
|
1266
|
+
} else if (readModels.length > 0) {
|
|
1267
|
+
console.log(chalk.yellow(`⚠ readModels: section found but no broker is installed. Run 'eva add kafka-client' to generate listener classes.`));
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1075
1271
|
console.log(chalk.blue('\n📦 Generated files:'));
|
|
1076
1272
|
const groupedFiles = generatedFiles.reduce((acc, file) => {
|
|
1077
1273
|
if (!acc[file.type]) acc[file.type] = [];
|
|
@@ -1113,8 +1309,18 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
1113
1309
|
}
|
|
1114
1310
|
}
|
|
1115
1311
|
if (!op._ownerAggregate) {
|
|
1116
|
-
// True scaffold: heuristic —
|
|
1117
|
-
|
|
1312
|
+
// True scaffold: heuristic — strip CRUD verb prefix and singularize to find
|
|
1313
|
+
// the best aggregate match via prefix comparison.
|
|
1314
|
+
// e.g. "FindAllGuarantees" → suffix "Guarantees" → singular "Guarantee"
|
|
1315
|
+
// → "GuaranteeCatalog".startsWith("guarantee") ✓
|
|
1316
|
+
const ucSuffix = op.useCase.replace(/^(FindAll|GetAll|Get|Create|Update|Delete|Add|Remove)/, '');
|
|
1317
|
+
const singularSuffix = ucSuffix ? singularizeWord(ucSuffix).toLowerCase() : '';
|
|
1318
|
+
const matched = (singularSuffix &&
|
|
1319
|
+
aggregates.find(agg => {
|
|
1320
|
+
const aggLower = agg.name.toLowerCase();
|
|
1321
|
+
return aggLower.startsWith(singularSuffix) || singularSuffix.startsWith(aggLower);
|
|
1322
|
+
})
|
|
1323
|
+
) || aggregates.find(agg =>
|
|
1118
1324
|
op.useCase.toLowerCase().includes(agg.name.toLowerCase())
|
|
1119
1325
|
);
|
|
1120
1326
|
const owner = matched || aggregates[0];
|
|
@@ -1434,6 +1640,34 @@ function classifyUseCase(op, aggregateName, aggregate) {
|
|
|
1434
1640
|
}
|
|
1435
1641
|
}
|
|
1436
1642
|
|
|
1643
|
+
// 5. Fuzzy FindAll — useCase starts with "FindAll" and the singular of the
|
|
1644
|
+
// suffix is a prefix of the aggregate name (or vice-versa).
|
|
1645
|
+
// e.g. FindAllGuarantees → singular "Guarantee" → "GuaranteeCatalog" starts with it ✓
|
|
1646
|
+
if (op.useCase.startsWith('FindAll')) {
|
|
1647
|
+
const suffix = op.useCase.slice(7);
|
|
1648
|
+
if (suffix) {
|
|
1649
|
+
const singularSuffix = singularizeWord(suffix).toLowerCase();
|
|
1650
|
+
const aggLower = aggregateName.toLowerCase();
|
|
1651
|
+
if (aggLower.startsWith(singularSuffix) || singularSuffix.startsWith(aggLower)) {
|
|
1652
|
+
return { category: 'standard', variant: 'findAll' };
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
// 6. Fuzzy Get — useCase starts with "Get" and the suffix matches the
|
|
1658
|
+
// aggregate name as a prefix (or vice-versa).
|
|
1659
|
+
// e.g. GetCatTariff → "CatTariff" matches aggregate "CatTariff" ✓
|
|
1660
|
+
if (op.useCase.startsWith('Get') && !op.useCase.startsWith('GetAll')) {
|
|
1661
|
+
const suffix = op.useCase.slice(3);
|
|
1662
|
+
if (suffix) {
|
|
1663
|
+
const suffixLower = suffix.toLowerCase();
|
|
1664
|
+
const aggLower = aggregateName.toLowerCase();
|
|
1665
|
+
if (aggLower.startsWith(suffixLower) || suffixLower.startsWith(aggLower)) {
|
|
1666
|
+
return { category: 'standard', variant: 'getById' };
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1437
1671
|
return { category: 'scaffold' };
|
|
1438
1672
|
}
|
|
1439
1673
|
|
|
@@ -1552,13 +1786,18 @@ async function generateEndpointsResources(aggregate, endpoints, moduleName, modu
|
|
|
1552
1786
|
const oneToManyRelationshipsApp = enrichRelsWithSchemaExamples(
|
|
1553
1787
|
transformRelsForApp(oneToManyRelationships, validatedVoNames), localAllEnums, valueObjects);
|
|
1554
1788
|
|
|
1789
|
+
const resolvedLifecycleEndpoints = resolveLifecycleEventArgs(
|
|
1790
|
+
aggregate.lifecycleEventsMap || {}, aggregateName, rootEntity.fields, valueObjects
|
|
1791
|
+
);
|
|
1555
1792
|
const baseContext = {
|
|
1556
1793
|
packageName, moduleName, aggregateName, aggregateNamePlural, rootEntity, secondaryEntities,
|
|
1557
1794
|
responseFields, responseSecondaryEntities, idType,
|
|
1558
1795
|
commandFields: commandFieldsApp, oneToManyRelationships, oneToOneRelationships,
|
|
1559
1796
|
hasValueObjects, hasEnums, imports: rootEntity.imports,
|
|
1560
1797
|
resourceNameCamel, resourceNameKebab,
|
|
1561
|
-
hasSoftDelete: rootEntity.hasSoftDelete || false
|
|
1798
|
+
hasSoftDelete: rootEntity.hasSoftDelete || false,
|
|
1799
|
+
domainEvents: aggregate.domainEvents || [],
|
|
1800
|
+
lifecycleEventsMap: resolvedLifecycleEndpoints
|
|
1562
1801
|
};
|
|
1563
1802
|
|
|
1564
1803
|
// ── Step 1: Validated VO Dtos ────────────────────────────────────────
|
|
@@ -2065,6 +2304,9 @@ async function generateCrudResources(aggregate, moduleName, moduleBasePath, pack
|
|
|
2065
2304
|
transformRelsForApp(oneToManyRelationships, validatedVoNames), localAllEnums, valueObjects);
|
|
2066
2305
|
|
|
2067
2306
|
// Base context for all templates
|
|
2307
|
+
const resolvedLifecycleCrud = resolveLifecycleEventArgs(
|
|
2308
|
+
aggregate.lifecycleEventsMap || {}, aggregateName, rootEntity.fields, valueObjects
|
|
2309
|
+
);
|
|
2068
2310
|
const baseContext = {
|
|
2069
2311
|
packageName,
|
|
2070
2312
|
moduleName,
|
|
@@ -2085,7 +2327,9 @@ async function generateCrudResources(aggregate, moduleName, moduleBasePath, pack
|
|
|
2085
2327
|
resourceNameCamel,
|
|
2086
2328
|
resourceNameKebab,
|
|
2087
2329
|
hasSoftDelete: rootEntity.hasSoftDelete || false,
|
|
2088
|
-
hasCreateOperation: true // In interactive CRUD flow, Create is always generated
|
|
2330
|
+
hasCreateOperation: true, // In interactive CRUD flow, Create is always generated
|
|
2331
|
+
domainEvents: aggregate.domainEvents || [],
|
|
2332
|
+
lifecycleEventsMap: resolvedLifecycleCrud
|
|
2089
2333
|
};
|
|
2090
2334
|
|
|
2091
2335
|
// 0. Generate Create<VoName>Dto for validated Value Objects
|
|
@@ -32,6 +32,9 @@ async function generateHttpExchangeCommand(moduleName, portName) {
|
|
|
32
32
|
const { packageName } = projectConfig;
|
|
33
33
|
const packagePath = toPackagePath(packageName);
|
|
34
34
|
|
|
35
|
+
// Normalise module name to camelCase (system.yaml uses kebab-case, .eva4j.json stores camelCase)
|
|
36
|
+
moduleName = toCamelCase(moduleName);
|
|
37
|
+
|
|
35
38
|
// Validate module exists
|
|
36
39
|
if (!(await configManager.moduleExists(moduleName))) {
|
|
37
40
|
console.error(chalk.red(`❌ Module '${moduleName}' not found in project configuration`));
|
|
@@ -40,6 +40,9 @@ async function generateKafkaEventCommand(moduleName, eventName) {
|
|
|
40
40
|
const { packageName } = projectConfig;
|
|
41
41
|
const packagePath = toPackagePath(packageName);
|
|
42
42
|
|
|
43
|
+
// Normalise module name to camelCase (system.yaml uses kebab-case, .eva4j.json stores camelCase)
|
|
44
|
+
moduleName = toCamelCase(moduleName);
|
|
45
|
+
|
|
43
46
|
// Validate module exists
|
|
44
47
|
if (!(await configManager.moduleExists(moduleName))) {
|
|
45
48
|
console.error(chalk.red(`❌ Module '${moduleName}' not found in project configuration`));
|
|
@@ -36,6 +36,9 @@ async function generateKafkaListenerCommand(moduleName) {
|
|
|
36
36
|
const { packageName, projectName } = projectConfig;
|
|
37
37
|
const packagePath = toPackagePath(packageName);
|
|
38
38
|
|
|
39
|
+
// Normalise module name to camelCase (system.yaml uses kebab-case, .eva4j.json stores camelCase)
|
|
40
|
+
moduleName = toCamelCase(moduleName);
|
|
41
|
+
|
|
39
42
|
// Validate module exists
|
|
40
43
|
if (!(await configManager.moduleExists(moduleName))) {
|
|
41
44
|
console.error(chalk.red(`❌ Module '${moduleName}' not found in project`));
|
|
@@ -6,7 +6,7 @@ const inquirer = require('inquirer');
|
|
|
6
6
|
const clipboardy = require('clipboardy');
|
|
7
7
|
const ConfigManager = require('../utils/config-manager');
|
|
8
8
|
const { isEva4jProject } = require('../utils/validator');
|
|
9
|
-
const { toPackagePath, toPascalCase } = require('../utils/naming');
|
|
9
|
+
const { toPackagePath, toPascalCase, toCamelCase } = require('../utils/naming');
|
|
10
10
|
const { renderAndWrite } = require('../utils/template-engine');
|
|
11
11
|
const { parseJsonToRecords } = require('../utils/json-to-java');
|
|
12
12
|
|
|
@@ -107,7 +107,7 @@ async function generateRecordCommand(options = {}) {
|
|
|
107
107
|
const { recordName, moduleName, targetFolder } = answers;
|
|
108
108
|
|
|
109
109
|
// Validate module exists in filesystem
|
|
110
|
-
const modulePath = path.join(projectDir, 'src', 'main', 'java', packagePath, moduleName);
|
|
110
|
+
const modulePath = path.join(projectDir, 'src', 'main', 'java', packagePath, toCamelCase(moduleName));
|
|
111
111
|
if (!(await fs.pathExists(modulePath))) {
|
|
112
112
|
console.error(chalk.red(`❌ Module '${moduleName}' not found in filesystem`));
|
|
113
113
|
process.exit(1);
|
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const fs = require('fs-extra');
|
|
6
6
|
const ConfigManager = require('../utils/config-manager');
|
|
7
7
|
const { isEva4jProject } = require('../utils/validator');
|
|
8
|
-
const { toPackagePath, toPascalCase, pluralizeWord, toKebabCase } = require('../utils/naming');
|
|
8
|
+
const { toPackagePath, toPascalCase, pluralizeWord, toKebabCase, toCamelCase } = require('../utils/naming');
|
|
9
9
|
const { renderAndWrite } = require('../utils/template-engine');
|
|
10
10
|
const ChecksumManager = require('../utils/checksum-manager');
|
|
11
11
|
|
|
@@ -30,6 +30,9 @@ async function generateResourceCommand(moduleName, options = {}) {
|
|
|
30
30
|
const { packageName } = projectConfig;
|
|
31
31
|
const packagePath = toPackagePath(packageName);
|
|
32
32
|
|
|
33
|
+
// Normalise module name to camelCase (system.yaml uses kebab-case, .eva4j.json stores camelCase)
|
|
34
|
+
moduleName = toCamelCase(moduleName);
|
|
35
|
+
|
|
33
36
|
// Validate module exists
|
|
34
37
|
if (!(await configManager.moduleExists(moduleName))) {
|
|
35
38
|
console.error(chalk.red(`❌ Module '${moduleName}' not found in project`));
|
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const fs = require('fs-extra');
|
|
6
6
|
const ConfigManager = require('../utils/config-manager');
|
|
7
7
|
const { isEva4jProject, moduleExists } = require('../utils/validator');
|
|
8
|
-
const { toPackagePath, toPascalCase } = require('../utils/naming');
|
|
8
|
+
const { toPackagePath, toPascalCase, toCamelCase } = require('../utils/naming');
|
|
9
9
|
const { renderAndWrite } = require('../utils/template-engine');
|
|
10
10
|
const ChecksumManager = require('../utils/checksum-manager');
|
|
11
11
|
|
|
@@ -46,6 +46,9 @@ async function generateTemporalActivityCommand(moduleName, activityName, options
|
|
|
46
46
|
const { packageName } = projectConfig;
|
|
47
47
|
const packagePath = toPackagePath(packageName);
|
|
48
48
|
|
|
49
|
+
// Normalise module name to camelCase (system.yaml uses kebab-case, .eva4j.json stores camelCase)
|
|
50
|
+
moduleName = toCamelCase(moduleName);
|
|
51
|
+
|
|
49
52
|
if (!(await configManager.moduleExists(moduleName))) {
|
|
50
53
|
console.error(chalk.red(`❌ Module '${moduleName}' does not exist`));
|
|
51
54
|
console.error(chalk.gray(`Create it first using: eva add module ${moduleName}`));
|
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const fs = require('fs-extra');
|
|
6
6
|
const ConfigManager = require('../utils/config-manager');
|
|
7
7
|
const { isEva4jProject, moduleExists } = require('../utils/validator');
|
|
8
|
-
const { toPackagePath, toPascalCase } = require('../utils/naming');
|
|
8
|
+
const { toPackagePath, toPascalCase, toCamelCase } = require('../utils/naming');
|
|
9
9
|
const { renderAndWrite } = require('../utils/template-engine');
|
|
10
10
|
const ChecksumManager = require('../utils/checksum-manager');
|
|
11
11
|
|
|
@@ -48,6 +48,9 @@ async function generateTemporalFlowCommand(moduleName, flowName, options = {}) {
|
|
|
48
48
|
const { packageName } = projectConfig;
|
|
49
49
|
const packagePath = toPackagePath(packageName);
|
|
50
50
|
|
|
51
|
+
// Normalise module name to camelCase (system.yaml uses kebab-case, .eva4j.json stores camelCase)
|
|
52
|
+
moduleName = toCamelCase(moduleName);
|
|
53
|
+
|
|
51
54
|
if (!(await configManager.moduleExists(moduleName))) {
|
|
52
55
|
console.error(chalk.red(`❌ Module '${moduleName}' does not exist`));
|
|
53
56
|
console.error(chalk.gray(`Create it first using: eva add module ${moduleName}`));
|
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const fs = require('fs-extra');
|
|
6
6
|
const ConfigManager = require('../utils/config-manager');
|
|
7
7
|
const { isEva4jProject, moduleExists } = require('../utils/validator');
|
|
8
|
-
const { toPackagePath, toPascalCase } = require('../utils/naming');
|
|
8
|
+
const { toPackagePath, toPascalCase, toCamelCase } = require('../utils/naming');
|
|
9
9
|
const { renderAndWrite } = require('../utils/template-engine');
|
|
10
10
|
const ChecksumManager = require('../utils/checksum-manager');
|
|
11
11
|
|
|
@@ -32,6 +32,9 @@ async function generateUsecaseCommand(moduleName, usecaseName, options = {}) {
|
|
|
32
32
|
const { packageName } = projectConfig;
|
|
33
33
|
const packagePath = toPackagePath(packageName);
|
|
34
34
|
|
|
35
|
+
// Normalise module name to camelCase (system.yaml uses kebab-case, .eva4j.json stores camelCase)
|
|
36
|
+
moduleName = toCamelCase(moduleName);
|
|
37
|
+
|
|
35
38
|
// Validate module exists
|
|
36
39
|
if (!(await configManager.moduleExists(moduleName))) {
|
|
37
40
|
console.error(chalk.red(`❌ Module '${moduleName}' not found in project configuration`));
|