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
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
package <%= packageName %>.shared.infrastructure.handlerException;
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
import <%= packageName %>.shared.domain.customExceptions.*;
|
|
5
|
-
import <%= packageName %>.shared.domain.errorMessage.
|
|
6
|
-
import <%= packageName %>.shared.domain.errorMessage.FullErrorMessage;
|
|
7
|
-
import <%= packageName %>.shared.domain.errorMessage.ShortErrorMessage;
|
|
4
|
+
import <%= packageName %>.shared.domain.errorMessage.ErrorResponse;
|
|
8
5
|
import feign.RetryableException;
|
|
9
6
|
import jakarta.validation.ConstraintViolationException;
|
|
7
|
+
import org.slf4j.Logger;
|
|
8
|
+
import org.slf4j.LoggerFactory;
|
|
10
9
|
import org.springframework.dao.DataIntegrityViolationException;
|
|
11
10
|
import org.springframework.http.HttpStatus;
|
|
12
11
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
|
@@ -25,140 +24,161 @@ import org.springframework.web.server.MethodNotAllowedException;
|
|
|
25
24
|
import org.springframework.web.server.ServerWebInputException;
|
|
26
25
|
import org.yaml.snakeyaml.constructor.DuplicateKeyException;
|
|
27
26
|
|
|
28
|
-
import java.util.ArrayList;
|
|
29
27
|
import java.util.List;
|
|
30
28
|
|
|
31
29
|
@RestControllerAdvice
|
|
32
30
|
public class HandlerExceptions {
|
|
33
31
|
|
|
32
|
+
private static final Logger log = LoggerFactory.getLogger(HandlerExceptions.class);
|
|
33
|
+
|
|
34
|
+
// āā Validation errors āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
35
|
+
|
|
34
36
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
|
35
37
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
36
38
|
@ResponseBody
|
|
37
|
-
public
|
|
38
|
-
List<String>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return new FullErrorMessage(messagesErrors,"BadRequest",HttpStatus.BAD_REQUEST.value());
|
|
39
|
+
public ErrorResponse onMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
|
|
40
|
+
List<String> details = ex.getBindingResult().getFieldErrors().stream()
|
|
41
|
+
.map(error -> error.getField() + " " + error.getDefaultMessage())
|
|
42
|
+
.toList();
|
|
43
|
+
return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Bad Request", "Validation failed", details);
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
|
|
46
46
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
|
47
47
|
@ExceptionHandler(ConstraintViolationException.class)
|
|
48
48
|
@ResponseBody
|
|
49
|
-
public
|
|
50
|
-
List<String>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return new FullErrorMessage(errors,"Bad Request",HttpStatus.BAD_REQUEST.value());
|
|
49
|
+
public ErrorResponse onConstraintValidationException(ConstraintViolationException ex) {
|
|
50
|
+
List<String> details = ex.getConstraintViolations().stream()
|
|
51
|
+
.map(v -> v.getMessage())
|
|
52
|
+
.toList();
|
|
53
|
+
return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Bad Request", "Constraint violation", details);
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
@
|
|
59
|
-
@ExceptionHandler({
|
|
60
|
-
DuplicateKeyException.class,
|
|
61
|
-
MethodNotAllowedException.class,
|
|
62
|
-
ServerWebInputException.class,
|
|
63
|
-
MethodArgumentTypeMismatchException.class,
|
|
64
|
-
HttpMessageNotReadableException.class,
|
|
65
|
-
DataIntegrityViolationException.class
|
|
66
|
-
})
|
|
56
|
+
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
|
|
57
|
+
@ExceptionHandler(ValidationException.class)
|
|
67
58
|
@ResponseBody
|
|
68
|
-
public
|
|
69
|
-
|
|
59
|
+
public ErrorResponse onValidationException(ValidationException ex) {
|
|
60
|
+
String message = ex.getRowNumber() != null
|
|
61
|
+
? "Row " + ex.getRowNumber() + ": " + ex.getMessage()
|
|
62
|
+
: ex.getMessage();
|
|
63
|
+
return new ErrorResponse(HttpStatus.UNPROCESSABLE_ENTITY.value(), "Validation Error", message);
|
|
70
64
|
}
|
|
71
65
|
|
|
72
|
-
|
|
73
|
-
@ExceptionHandler(
|
|
74
|
-
AuthorizationDeniedException.class
|
|
75
|
-
)
|
|
76
|
-
@ResponseBody
|
|
77
|
-
public ErrorMessage onAuthorizationDeniedException( AuthorizationDeniedException ex) {
|
|
78
|
-
if (ex.getMessage() == null) return new ShortErrorMessage("Forbidden", HttpStatus.FORBIDDEN.value());
|
|
79
|
-
return new FullErrorMessage(List.of(ex.getMessage()), "Forbidden", HttpStatus.FORBIDDEN.value());
|
|
80
|
-
}
|
|
66
|
+
// āā Spring framework errors āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
81
67
|
|
|
82
|
-
// custom exceptions
|
|
83
|
-
//------------------
|
|
84
68
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
|
85
|
-
@ExceptionHandler(
|
|
69
|
+
@ExceptionHandler({
|
|
70
|
+
DuplicateKeyException.class,
|
|
71
|
+
MethodNotAllowedException.class,
|
|
72
|
+
ServerWebInputException.class,
|
|
73
|
+
MethodArgumentTypeMismatchException.class,
|
|
74
|
+
HttpMessageNotReadableException.class
|
|
75
|
+
})
|
|
86
76
|
@ResponseBody
|
|
87
|
-
public
|
|
88
|
-
|
|
89
|
-
return new FullErrorMessage(List.of(ex.getMessage()),"Bad Request", HttpStatus.BAD_REQUEST.value());
|
|
77
|
+
public ErrorResponse onSpringBadRequest(Exception ex) {
|
|
78
|
+
return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Bad Request", "Malformed request");
|
|
90
79
|
}
|
|
91
80
|
|
|
92
|
-
@ResponseStatus(HttpStatus.
|
|
93
|
-
@ExceptionHandler(
|
|
81
|
+
@ResponseStatus(HttpStatus.CONFLICT)
|
|
82
|
+
@ExceptionHandler(DataIntegrityViolationException.class)
|
|
94
83
|
@ResponseBody
|
|
95
|
-
public
|
|
96
|
-
|
|
97
|
-
return new FullErrorMessage(List.of(ex.getMessage()), "Not Found", HttpStatus.NOT_FOUND.value());
|
|
84
|
+
public ErrorResponse onDataIntegrityViolation(DataIntegrityViolationException ex) {
|
|
85
|
+
return new ErrorResponse(HttpStatus.CONFLICT.value(), "Conflict", "Data integrity violation ā a constraint was not satisfied");
|
|
98
86
|
}
|
|
99
87
|
|
|
88
|
+
// āā Security errors āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
89
|
+
|
|
100
90
|
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
|
101
91
|
@ExceptionHandler({
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
92
|
+
UnauthorizedException.class,
|
|
93
|
+
BadCredentialsException.class,
|
|
94
|
+
DisabledException.class,
|
|
95
|
+
LockedException.class,
|
|
96
|
+
InternalAuthenticationServiceException.class
|
|
107
97
|
})
|
|
108
98
|
@ResponseBody
|
|
109
|
-
public
|
|
110
|
-
|
|
111
|
-
|
|
99
|
+
public ErrorResponse onUnauthorizedException(Exception ex) {
|
|
100
|
+
return new ErrorResponse(HttpStatus.UNAUTHORIZED.value(), "Unauthorized",
|
|
101
|
+
ex.getMessage() != null ? ex.getMessage() : "Authentication required");
|
|
112
102
|
}
|
|
113
103
|
|
|
114
104
|
@ResponseStatus(HttpStatus.FORBIDDEN)
|
|
115
|
-
@ExceptionHandler(
|
|
116
|
-
ForbiddenException.class
|
|
117
|
-
)
|
|
105
|
+
@ExceptionHandler({ForbiddenException.class, AuthorizationDeniedException.class})
|
|
118
106
|
@ResponseBody
|
|
119
|
-
public
|
|
120
|
-
|
|
121
|
-
|
|
107
|
+
public ErrorResponse onForbiddenException(Exception ex) {
|
|
108
|
+
return new ErrorResponse(HttpStatus.FORBIDDEN.value(), "Forbidden",
|
|
109
|
+
ex.getMessage() != null ? ex.getMessage() : "Access denied");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// āā Custom domain exceptions āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
113
|
+
|
|
114
|
+
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
|
115
|
+
@ExceptionHandler(BadRequestException.class)
|
|
116
|
+
@ResponseBody
|
|
117
|
+
public ErrorResponse onBadRequestException(BadRequestException ex) {
|
|
118
|
+
return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Bad Request",
|
|
119
|
+
ex.getMessage() != null ? ex.getMessage() : "Bad request");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
|
123
|
+
@ExceptionHandler(ImportFileException.class)
|
|
124
|
+
@ResponseBody
|
|
125
|
+
public ErrorResponse onImportFileException(ImportFileException ex) {
|
|
126
|
+
return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Import Error",
|
|
127
|
+
ex.getMessage() != null ? ex.getMessage() : "File import failed");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@ResponseStatus(HttpStatus.NOT_FOUND)
|
|
131
|
+
@ExceptionHandler(NotFoundException.class)
|
|
132
|
+
@ResponseBody
|
|
133
|
+
public ErrorResponse onNotFoundException(NotFoundException ex) {
|
|
134
|
+
return new ErrorResponse(HttpStatus.NOT_FOUND.value(), "Not Found",
|
|
135
|
+
ex.getMessage() != null ? ex.getMessage() : "Resource not found");
|
|
122
136
|
}
|
|
123
137
|
|
|
124
138
|
@ResponseStatus(HttpStatus.CONFLICT)
|
|
125
139
|
@ExceptionHandler(ConflictException.class)
|
|
126
140
|
@ResponseBody
|
|
127
|
-
public
|
|
128
|
-
|
|
129
|
-
|
|
141
|
+
public ErrorResponse onConflictException(ConflictException ex) {
|
|
142
|
+
return new ErrorResponse(HttpStatus.CONFLICT.value(), "Conflict",
|
|
143
|
+
ex.getMessage() != null ? ex.getMessage() : "Resource conflict");
|
|
130
144
|
}
|
|
131
145
|
|
|
132
146
|
@ResponseStatus(HttpStatus.CONFLICT)
|
|
133
147
|
@ExceptionHandler(InvalidStateTransitionException.class)
|
|
134
148
|
@ResponseBody
|
|
135
|
-
public
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
public ErrorResponse onInvalidStateTransitionException(InvalidStateTransitionException ex) {
|
|
150
|
+
return new ErrorResponse(HttpStatus.CONFLICT.value(), "Invalid State Transition",
|
|
151
|
+
ex.getMessage() != null ? ex.getMessage() : "State transition not allowed");
|
|
138
152
|
}
|
|
139
153
|
|
|
140
154
|
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
|
|
141
155
|
@ExceptionHandler(BusinessException.class)
|
|
142
156
|
@ResponseBody
|
|
143
|
-
public
|
|
144
|
-
|
|
145
|
-
|
|
157
|
+
public ErrorResponse onBusinessException(BusinessException ex) {
|
|
158
|
+
return new ErrorResponse(HttpStatus.UNPROCESSABLE_ENTITY.value(), "Business Rule Violation",
|
|
159
|
+
ex.getMessage() != null ? ex.getMessage() : "A business rule was violated");
|
|
146
160
|
}
|
|
147
161
|
|
|
148
|
-
//
|
|
162
|
+
// āā External service errors āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
163
|
+
|
|
149
164
|
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
|
|
150
165
|
@ExceptionHandler(RetryableException.class)
|
|
151
166
|
@ResponseBody
|
|
152
|
-
public
|
|
153
|
-
|
|
167
|
+
public ErrorResponse onRetryableException(RetryableException ex) {
|
|
168
|
+
log.error("External service unavailable: {}", ex.request().url(), ex);
|
|
169
|
+
return new ErrorResponse(HttpStatus.SERVICE_UNAVAILABLE.value(), "Service Unavailable",
|
|
170
|
+
"An external service is temporarily unavailable");
|
|
154
171
|
}
|
|
155
172
|
|
|
156
|
-
//
|
|
173
|
+
// āā Catch-all āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
174
|
+
|
|
157
175
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
158
176
|
@ExceptionHandler(Exception.class)
|
|
159
177
|
@ResponseBody
|
|
160
|
-
public
|
|
161
|
-
|
|
178
|
+
public ErrorResponse onServerError(Exception ex) {
|
|
179
|
+
log.error("Unhandled exception", ex);
|
|
180
|
+
return new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error",
|
|
181
|
+
"An unexpected error occurred");
|
|
162
182
|
}
|
|
163
183
|
|
|
164
184
|
}
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const fs = require('fs-extra');
|
|
6
|
-
const yaml = require('js-yaml');
|
|
7
|
-
const pluralize = require('pluralize');
|
|
8
|
-
|
|
9
|
-
const ConfigManager = require('../utils/config-manager');
|
|
10
|
-
const { isEva4jProject } = require('../utils/validator');
|
|
11
|
-
const { toCamelCase, toPascalCase, toPackagePath } = require('../utils/naming');
|
|
12
|
-
|
|
13
|
-
const addModuleCommand = require('./add-module');
|
|
14
|
-
const addKafkaClientCommand = require('./add-kafka-client');
|
|
15
|
-
|
|
16
|
-
// Supported brokers ā add-client command mapping
|
|
17
|
-
const BROKER_CLIENT_COMMANDS = {
|
|
18
|
-
kafka: addKafkaClientCommand,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
async function generateSystemCommand() {
|
|
22
|
-
const projectDir = process.cwd();
|
|
23
|
-
|
|
24
|
-
if (!(await isEva4jProject(projectDir))) {
|
|
25
|
-
console.error(chalk.red('ā Not in an eva4j project directory'));
|
|
26
|
-
console.error(chalk.gray('Run this command inside a project created with eva4j'));
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// āā Read system.yaml āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
31
|
-
const systemYamlPath = path.join(projectDir, 'system.yaml');
|
|
32
|
-
if (!(await fs.pathExists(systemYamlPath))) {
|
|
33
|
-
console.error(chalk.red('ā system.yaml not found in project root'));
|
|
34
|
-
console.error(chalk.gray('Create a system.yaml file first'));
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let systemConfig;
|
|
39
|
-
try {
|
|
40
|
-
const content = await fs.readFile(systemYamlPath, 'utf-8');
|
|
41
|
-
systemConfig = yaml.load(content);
|
|
42
|
-
} catch (err) {
|
|
43
|
-
console.error(chalk.red('ā Failed to parse system.yaml:'), err.message);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const { messaging, modules = [] } = systemConfig;
|
|
48
|
-
|
|
49
|
-
if (!modules.length) {
|
|
50
|
-
console.log(chalk.yellow('ā ļø No modules defined in system.yaml'));
|
|
51
|
-
process.exit(0);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
console.log(chalk.blue('\nš eva generate system\n'));
|
|
55
|
-
|
|
56
|
-
const configManager = new ConfigManager(projectDir);
|
|
57
|
-
|
|
58
|
-
// āā Step 1: Add modules āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
59
|
-
console.log(chalk.blue('\nš¦ Adding modules...\n'));
|
|
60
|
-
|
|
61
|
-
for (const mod of modules) {
|
|
62
|
-
const modulePackageName = toCamelCase(mod.name);
|
|
63
|
-
const alreadyExists = await configManager.moduleExists(modulePackageName);
|
|
64
|
-
|
|
65
|
-
if (alreadyExists) {
|
|
66
|
-
console.log(chalk.gray(` ā Module '${mod.name}' already exists, skipping`));
|
|
67
|
-
} else {
|
|
68
|
-
await addModuleCommand(mod.name, {});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// āā Step 2: Messaging broker client āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
73
|
-
if (messaging && messaging.enabled === true) {
|
|
74
|
-
const broker = messaging.broker;
|
|
75
|
-
const addClientFn = BROKER_CLIENT_COMMANDS[broker];
|
|
76
|
-
|
|
77
|
-
if (!addClientFn) {
|
|
78
|
-
console.log(chalk.yellow(` ā ļø Broker '${broker}' is not yet supported. Skipping client setup.`));
|
|
79
|
-
} else {
|
|
80
|
-
const alreadyInstalled = await configManager.featureExists(broker);
|
|
81
|
-
if (alreadyInstalled) {
|
|
82
|
-
console.log(chalk.gray(` ā ${broker}-client already installed, skipping\n`));
|
|
83
|
-
} else {
|
|
84
|
-
console.log(chalk.blue(`\nš” Adding ${broker}-client...\n`));
|
|
85
|
-
await addClientFn();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// āā Step 3: Generate domain.yaml per module āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
91
|
-
const projectConfig = await configManager.loadProjectConfig();
|
|
92
|
-
if (!projectConfig) {
|
|
93
|
-
console.error(chalk.red('ā Could not load project configuration'));
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const packagePath = toPackagePath(projectConfig.packageName);
|
|
98
|
-
|
|
99
|
-
console.log(chalk.blue('\nš Generating domain.yaml skeletons...\n'));
|
|
100
|
-
|
|
101
|
-
for (const mod of modules) {
|
|
102
|
-
const modulePackageName = toCamelCase(mod.name);
|
|
103
|
-
const moduleDir = path.join(projectDir, 'src', 'main', 'java', packagePath, modulePackageName);
|
|
104
|
-
const domainYamlPath = path.join(moduleDir, 'domain.yaml');
|
|
105
|
-
|
|
106
|
-
const existed = await fs.pathExists(domainYamlPath);
|
|
107
|
-
const content = buildDomainYaml(mod, systemConfig);
|
|
108
|
-
await fs.writeFile(domainYamlPath, content, 'utf-8');
|
|
109
|
-
|
|
110
|
-
if (existed) {
|
|
111
|
-
console.log(chalk.yellow(` ā»ļø ${modulePackageName}/domain.yaml overwritten`));
|
|
112
|
-
} else {
|
|
113
|
-
console.log(chalk.green(` ⨠${modulePackageName}/domain.yaml created`));
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
console.log(chalk.blue('\nā
System bootstrap complete!\n'));
|
|
118
|
-
console.log(chalk.white('Next steps:'));
|
|
119
|
-
console.log(chalk.gray(" 1. Edit each module's domain.yaml ā add fields, enums, and refine the aggregate"));
|
|
120
|
-
console.log(chalk.gray(' 2. Run: eva g entities <module> (for each module)'));
|
|
121
|
-
console.log(chalk.gray('\n Tip: run eva system validate to check cross-module consistency'));
|
|
122
|
-
console.log();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// āā Domain YAML builder āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
126
|
-
|
|
127
|
-
function buildDomainYaml(mod, systemConfig) {
|
|
128
|
-
const integrations = systemConfig.integrations || {};
|
|
129
|
-
const asyncEvents = integrations.async || [];
|
|
130
|
-
const syncCalls = integrations.sync || [];
|
|
131
|
-
|
|
132
|
-
const moduleName = mod.name;
|
|
133
|
-
const aggregateName = toPascalCase(pluralize.singular(moduleName));
|
|
134
|
-
const entityName = aggregateName.charAt(0).toLowerCase() + aggregateName.slice(1);
|
|
135
|
-
const tableName = moduleName.replace(/-/g, '_');
|
|
136
|
-
|
|
137
|
-
// Events this module produces
|
|
138
|
-
const producedEvents = asyncEvents.filter(e => e.producer === moduleName);
|
|
139
|
-
// Sync calls this module makes as caller
|
|
140
|
-
const outboundPorts = syncCalls.filter(s => s.caller === moduleName);
|
|
141
|
-
// REST endpoints exposed by this module
|
|
142
|
-
const exposes = mod.exposes || [];
|
|
143
|
-
|
|
144
|
-
const lines = [];
|
|
145
|
-
const today = new Date().toISOString().split('T')[0];
|
|
146
|
-
|
|
147
|
-
lines.push(`# domain.yaml ā ${moduleName}`);
|
|
148
|
-
lines.push(`# Generated by: eva generate system (${today})`);
|
|
149
|
-
lines.push(`#`);
|
|
150
|
-
lines.push(`# TODO: Complete this file:`);
|
|
151
|
-
lines.push(`# - Add entity fields under aggregates[].entities[].fields`);
|
|
152
|
-
if (producedEvents.length) lines.push(`# - Add event fields under aggregates[].events[].fields`);
|
|
153
|
-
if (outboundPorts.length) lines.push(`# - Add response shapes to ports[].methods[].response`);
|
|
154
|
-
if (exposes.length) lines.push(`# - Verify endpoints[].operations match the use cases in aggregates[]`);
|
|
155
|
-
lines.push(``);
|
|
156
|
-
|
|
157
|
-
// āā aggregates āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
158
|
-
lines.push(`aggregates:`);
|
|
159
|
-
lines.push(` - name: ${aggregateName}`);
|
|
160
|
-
lines.push(` entities:`);
|
|
161
|
-
lines.push(` - name: ${entityName}`);
|
|
162
|
-
lines.push(` isRoot: true`);
|
|
163
|
-
lines.push(` tableName: ${tableName}`);
|
|
164
|
-
lines.push(` audit:`);
|
|
165
|
-
lines.push(` enabled: true`);
|
|
166
|
-
lines.push(` fields:`);
|
|
167
|
-
lines.push(` - name: id`);
|
|
168
|
-
lines.push(` type: String`);
|
|
169
|
-
lines.push(` # TODO: add more fields`);
|
|
170
|
-
|
|
171
|
-
if (producedEvents.length) {
|
|
172
|
-
lines.push(` events:`);
|
|
173
|
-
for (const ev of producedEvents) {
|
|
174
|
-
lines.push(` - name: ${ev.event}`);
|
|
175
|
-
lines.push(` fields: [] # TODO: add event fields`);
|
|
176
|
-
lines.push(` kafka: true`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
lines.push(``);
|
|
181
|
-
|
|
182
|
-
// āā endpoints āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
183
|
-
if (exposes.length) {
|
|
184
|
-
// Derive basePath from the first exposed endpoint (e.g. /orders/{id} ā /orders)
|
|
185
|
-
const basePath = '/' + (exposes[0].path || '').replace(/^\//, '').split('/')[0];
|
|
186
|
-
lines.push(`endpoints:`);
|
|
187
|
-
lines.push(` basePath: ${basePath}`);
|
|
188
|
-
lines.push(` versions:`);
|
|
189
|
-
lines.push(` - version: v1`);
|
|
190
|
-
lines.push(` operations:`);
|
|
191
|
-
for (const ep of exposes) {
|
|
192
|
-
lines.push(` - useCase: ${ep.useCase}`);
|
|
193
|
-
lines.push(` method: ${ep.method}`);
|
|
194
|
-
lines.push(` path: ${ep.path}`);
|
|
195
|
-
if (ep.description) lines.push(` description: "${ep.description}"`);
|
|
196
|
-
}
|
|
197
|
-
lines.push(``);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// āā ports āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
201
|
-
if (outboundPorts.length) {
|
|
202
|
-
lines.push(`ports:`);
|
|
203
|
-
for (const port of outboundPorts) {
|
|
204
|
-
lines.push(` - name: ${port.port}`);
|
|
205
|
-
lines.push(` target: ${port.calls} # from system.yaml integrations.sync`);
|
|
206
|
-
lines.push(` methods:`);
|
|
207
|
-
for (const endpoint of (port.using || [])) {
|
|
208
|
-
const methodName = deriveMethodName(endpoint);
|
|
209
|
-
lines.push(` - name: ${methodName} # TODO: rename if needed`);
|
|
210
|
-
lines.push(` http: ${endpoint}`);
|
|
211
|
-
lines.push(` response: [] # TODO: add response fields`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
lines.push(``);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return lines.join('\n');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Derive a camelCase Java method name from an HTTP entry like "GET /customers/{id}"
|
|
222
|
-
*/
|
|
223
|
-
function deriveMethodName(httpEntry) {
|
|
224
|
-
const parts = httpEntry.trim().split(/\s+/);
|
|
225
|
-
const method = (parts[0] || 'GET').toUpperCase();
|
|
226
|
-
const urlPath = parts[1] || '/';
|
|
227
|
-
|
|
228
|
-
const segments = urlPath.split('/').filter(s => s.length > 0);
|
|
229
|
-
const hasId = segments.some(s => s.charAt(0) === '{');
|
|
230
|
-
const resourceSegments = segments.filter(s => s.charAt(0) !== '{');
|
|
231
|
-
const lastResource = resourceSegments[resourceSegments.length - 1] || 'resource';
|
|
232
|
-
const singular = pluralize.singular(lastResource);
|
|
233
|
-
const pascal = toPascalCase(singular);
|
|
234
|
-
|
|
235
|
-
if (method === 'GET' && hasId) return `find${pascal}ById`;
|
|
236
|
-
if (method === 'GET') return `findAll${toPascalCase(lastResource)}`;
|
|
237
|
-
if (method === 'POST') return `create${pascal}`;
|
|
238
|
-
if (method === 'PUT' || method === 'PATCH') return `update${pascal}`;
|
|
239
|
-
if (method === 'DELETE') return `delete${pascal}`;
|
|
240
|
-
return toCamelCase(`${method.toLowerCase()}_${lastResource}`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
module.exports = generateSystemCommand;
|