eva4j 1.0.13 ā 1.0.15
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 +314 -10
- package/COMMAND_EVALUATION.md +15 -16
- package/DOMAIN_YAML_GUIDE.md +576 -10
- package/FUTURE_FEATURES.md +1627 -1168
- package/README.md +318 -13
- package/bin/eva4j.js +34 -0
- package/config/defaults.json +1 -0
- package/design-system.md +797 -0
- package/docs/commands/EVALUATE_SYSTEM.md +994 -0
- package/docs/commands/GENERATE_ENTITIES.md +795 -6
- package/docs/commands/INDEX.md +10 -1
- package/examples/domain-endpoints-relations.yaml +353 -0
- package/examples/domain-endpoints-versioned.yaml +144 -0
- package/examples/domain-endpoints.yaml +135 -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 +332 -0
- package/package.json +2 -1
- package/src/commands/build.js +714 -0
- package/src/commands/create.js +7 -3
- package/src/commands/detach.js +1 -0
- package/src/commands/evaluate-system.js +610 -0
- package/src/commands/generate-entities.js +1331 -49
- package/src/commands/generate-http-exchange.js +2 -0
- package/src/commands/generate-kafka-event.js +98 -11
- package/src/generators/base-generator.js +8 -1
- package/src/generators/postman-generator.js +188 -0
- package/src/generators/shared-generator.js +10 -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 +970 -0
- package/src/utils/fake-data.js +376 -0
- package/src/utils/naming.js +3 -2
- package/src/utils/system-validator.js +434 -0
- package/src/utils/yaml-to-entity.js +302 -8
- package/templates/aggregate/AggregateMapper.java.ejs +3 -2
- package/templates/aggregate/AggregateRepository.java.ejs +8 -2
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +13 -3
- package/templates/aggregate/AggregateRoot.java.ejs +60 -2
- package/templates/aggregate/DomainEventHandler.java.ejs +27 -20
- 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/aggregate/JpaRepository.java.ejs +5 -0
- package/templates/base/gradle/build.gradle.ejs +3 -2
- package/templates/base/root/AGENTS.md.ejs +306 -45
- package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +1663 -0
- package/templates/base/root/skill-build-system-yaml.ejs +1446 -0
- package/templates/base/root/system.yaml.ejs +97 -0
- 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/CreateItemDto.java.ejs +4 -0
- package/templates/crud/CreateValueObjectDto.java.ejs +4 -0
- package/templates/crud/DeleteCommandHandler.java.ejs +10 -2
- package/templates/crud/EndpointsController.java.ejs +178 -0
- package/templates/crud/FindByQuery.java.ejs +17 -0
- package/templates/crud/FindByQueryHandler.java.ejs +57 -0
- package/templates/crud/ListQuery.java.ejs +1 -1
- package/templates/crud/ListQueryHandler.java.ejs +8 -8
- package/templates/crud/ScaffoldCommand.java.ejs +12 -0
- package/templates/crud/ScaffoldCommandHandler.java.ejs +43 -0
- package/templates/crud/ScaffoldQuery.java.ejs +13 -0
- package/templates/crud/ScaffoldQueryHandler.java.ejs +41 -0
- package/templates/crud/SubEntityAddCommand.java.ejs +21 -0
- package/templates/crud/SubEntityAddCommandHandler.java.ejs +43 -0
- package/templates/crud/SubEntityRemoveCommand.java.ejs +9 -0
- package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +42 -0
- package/templates/crud/TransitionCommand.java.ejs +9 -0
- package/templates/crud/TransitionCommandHandler.java.ejs +39 -0
- package/templates/crud/UpdateCommand.java.ejs +4 -0
- package/templates/evaluate/report.html.ejs +1363 -0
- package/templates/kafka-event/DomainEventHandlerMethod.ejs +3 -1
- package/templates/kafka-event/Event.java.ejs +16 -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 +23 -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/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs +109 -0
|
@@ -0,0 +1,714 @@
|
|
|
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
|
+
|
|
8
|
+
const ConfigManager = require('../utils/config-manager');
|
|
9
|
+
const { isEva4jProject } = require('../utils/validator');
|
|
10
|
+
const { toCamelCase, toPackagePath } = require('../utils/naming');
|
|
11
|
+
const SharedGenerator = require('../generators/shared-generator');
|
|
12
|
+
const { renderAndWrite } = require('../utils/template-engine');
|
|
13
|
+
const addModuleCommand = require('./add-module');
|
|
14
|
+
const addKafkaClientCommand = require('./add-kafka-client');
|
|
15
|
+
const generateEntitiesCommand = require('./generate-entities');
|
|
16
|
+
const { generateUnifiedPostmanCollection } = require('../generators/postman-generator');
|
|
17
|
+
|
|
18
|
+
// āā H2 mock config āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
19
|
+
const H2_DB_YAML = (packageName) => `spring:
|
|
20
|
+
datasource:
|
|
21
|
+
url: jdbc:h2:file:./data/mockdb;AUTO_SERVER=TRUE
|
|
22
|
+
username: sa
|
|
23
|
+
password: ''
|
|
24
|
+
driver-class-name: org.h2.Driver
|
|
25
|
+
h2:
|
|
26
|
+
console:
|
|
27
|
+
enabled: true
|
|
28
|
+
jpa:
|
|
29
|
+
hibernate:
|
|
30
|
+
ddl-auto: update
|
|
31
|
+
show-sql: true
|
|
32
|
+
properties:
|
|
33
|
+
hibernate:
|
|
34
|
+
format_sql: true
|
|
35
|
+
dialect: org.hibernate.dialect.H2Dialect
|
|
36
|
+
|
|
37
|
+
logging:
|
|
38
|
+
level:
|
|
39
|
+
root: INFO
|
|
40
|
+
${packageName}: DEBUG
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const H2_GRADLE_LINE = ` runtimeOnly 'com.h2database:h2'`;
|
|
44
|
+
const ENVS = ['local', 'develop', 'test', 'production'];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Rebuild db context from projectConfig fields (mirrors detach.js logic).
|
|
48
|
+
*/
|
|
49
|
+
function buildDbContext(projectConfig) {
|
|
50
|
+
const databaseType = projectConfig.databaseType || 'postgresql';
|
|
51
|
+
const databaseName = (projectConfig.artifactId || projectConfig.projectName || 'app').replace(/-/g, '_');
|
|
52
|
+
|
|
53
|
+
const dbMap = {
|
|
54
|
+
h2: {
|
|
55
|
+
driver: 'com.h2.database:h2',
|
|
56
|
+
driverClass: 'org.h2.Driver',
|
|
57
|
+
url: `jdbc:h2:mem:${databaseName}`,
|
|
58
|
+
username: 'sa',
|
|
59
|
+
password: '',
|
|
60
|
+
hibernateDialect: 'org.hibernate.dialect.H2Dialect',
|
|
61
|
+
},
|
|
62
|
+
postgresql: {
|
|
63
|
+
driver: 'org.postgresql:postgresql',
|
|
64
|
+
driverClass: 'org.postgresql.Driver',
|
|
65
|
+
url: `jdbc:postgresql://localhost:5432/${databaseName}`,
|
|
66
|
+
username: 'postgres',
|
|
67
|
+
password: 'postgres',
|
|
68
|
+
hibernateDialect: 'org.hibernate.dialect.PostgreSQLDialect',
|
|
69
|
+
},
|
|
70
|
+
mysql: {
|
|
71
|
+
driver: 'com.mysql:mysql-connector-j',
|
|
72
|
+
driverClass: 'com.mysql.cj.jdbc.Driver',
|
|
73
|
+
url: `jdbc:mysql://localhost:3306/${databaseName}`,
|
|
74
|
+
username: 'root',
|
|
75
|
+
password: 'root',
|
|
76
|
+
hibernateDialect: 'org.hibernate.dialect.MySQLDialect',
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const db = dbMap[databaseType] || dbMap.postgresql;
|
|
81
|
+
return {
|
|
82
|
+
dependencies: projectConfig.dependencies || ['data-jpa'],
|
|
83
|
+
packageName: projectConfig.packageName,
|
|
84
|
+
databaseType,
|
|
85
|
+
databaseName,
|
|
86
|
+
databaseDriverClass: db.driverClass,
|
|
87
|
+
databaseUrl: db.url,
|
|
88
|
+
databaseUsername: db.username,
|
|
89
|
+
databasePassword: db.password,
|
|
90
|
+
hibernateDialect: db.hibernateDialect,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Regenerate db.yaml files from EJS templates using project's original DB config.
|
|
96
|
+
* Guarantees correctness even if backup contained stale/wrong content.
|
|
97
|
+
*/
|
|
98
|
+
async function regenerateDbYaml(projectDir, projectConfig) {
|
|
99
|
+
const dbContext = buildDbContext(projectConfig);
|
|
100
|
+
const templatesDir = path.join(__dirname, '../../templates/base');
|
|
101
|
+
const resourcesPath = path.join(projectDir, 'src', 'main', 'resources');
|
|
102
|
+
|
|
103
|
+
for (const env of ENVS) {
|
|
104
|
+
const templatePath = path.join(templatesDir, 'resources', 'parameters', env, 'db.yaml.ejs');
|
|
105
|
+
const destPath = path.join(resourcesPath, 'parameters', env, 'db.yaml');
|
|
106
|
+
if (await fs.pathExists(templatePath)) {
|
|
107
|
+
await renderAndWrite(templatePath, destPath, dbContext);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Rebuild the runtimeOnly DB driver line in build.gradle from project's original DB type.
|
|
114
|
+
*/
|
|
115
|
+
async function regenerateBuildGradleDbDriver(projectDir, projectConfig) {
|
|
116
|
+
const databaseType = projectConfig.databaseType || 'postgresql';
|
|
117
|
+
const driverMap = {
|
|
118
|
+
h2: ` runtimeOnly 'com.h2.database:h2'`,
|
|
119
|
+
postgresql: ` runtimeOnly 'org.postgresql:postgresql'`,
|
|
120
|
+
mysql: ` runtimeOnly 'com.mysql:mysql-connector-j'`,
|
|
121
|
+
};
|
|
122
|
+
const correctLine = driverMap[databaseType] || driverMap.postgresql;
|
|
123
|
+
|
|
124
|
+
const buildGradlePath = path.join(projectDir, 'build.gradle');
|
|
125
|
+
if (await fs.pathExists(buildGradlePath)) {
|
|
126
|
+
const current = await fs.readFile(buildGradlePath, 'utf-8');
|
|
127
|
+
const fixed = current.replace(
|
|
128
|
+
/^[ \t]*runtimeOnly\s+['"][^'"]+['"]\s*(?:\/\/.*)?$/m,
|
|
129
|
+
correctLine
|
|
130
|
+
);
|
|
131
|
+
await fs.writeFile(buildGradlePath, fixed, 'utf-8');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const H2_SECURITY_CONFIG = (packageName) => {
|
|
136
|
+
const S = '$'; // prevent JS template interpolation of Spring EL ${...}
|
|
137
|
+
return `package ${packageName}.shared.infrastructure.configurations.securityConfig;
|
|
138
|
+
|
|
139
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
140
|
+
import org.springframework.context.annotation.Bean;
|
|
141
|
+
import org.springframework.context.annotation.Configuration;
|
|
142
|
+
import org.springframework.security.config.Customizer;
|
|
143
|
+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
|
144
|
+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
145
|
+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
146
|
+
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
147
|
+
import org.springframework.security.web.SecurityFilterChain;
|
|
148
|
+
import org.springframework.web.cors.CorsConfiguration;
|
|
149
|
+
import org.springframework.web.cors.CorsConfigurationSource;
|
|
150
|
+
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
|
151
|
+
|
|
152
|
+
import java.util.List;
|
|
153
|
+
|
|
154
|
+
// ā” MOCK MODE ā generated by eva build --mock
|
|
155
|
+
// Restored automatically on next eva build (without --mock)
|
|
156
|
+
@EnableWebSecurity
|
|
157
|
+
@EnableMethodSecurity
|
|
158
|
+
@Configuration
|
|
159
|
+
public class SecurityConfig {
|
|
160
|
+
@Value("#{'${S}{cors.allowedOrigins}'.split(',')}")
|
|
161
|
+
private List<String> allowedOrigins;
|
|
162
|
+
|
|
163
|
+
@Value("#{'${S}{cors.allowedMethods}'.split(',')}")
|
|
164
|
+
private List<String> allowedMethods;
|
|
165
|
+
|
|
166
|
+
@Value("#{'${S}{cors.allowedHeaders}'.split(',')}")
|
|
167
|
+
private List<String> allowedHeaders;
|
|
168
|
+
|
|
169
|
+
private List<String> removeWhiteSpace(List<String> list) {
|
|
170
|
+
return list.stream().map(String::trim).toList();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@Bean
|
|
174
|
+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
175
|
+
http
|
|
176
|
+
.csrf(csrf -> csrf.disable())
|
|
177
|
+
.cors(Customizer.withDefaults())
|
|
178
|
+
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
|
179
|
+
.authorizeHttpRequests(auth -> auth
|
|
180
|
+
.requestMatchers("/h2-console/**").permitAll()
|
|
181
|
+
.anyRequest().permitAll()
|
|
182
|
+
)
|
|
183
|
+
.headers(headers -> headers
|
|
184
|
+
.frameOptions(frame -> frame.sameOrigin())
|
|
185
|
+
);
|
|
186
|
+
return http.build();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@Bean
|
|
190
|
+
CorsConfigurationSource corsConfigurationSource() {
|
|
191
|
+
CorsConfiguration configuration = new CorsConfiguration();
|
|
192
|
+
configuration.setAllowedOrigins(removeWhiteSpace(allowedOrigins));
|
|
193
|
+
configuration.setAllowedMethods(removeWhiteSpace(allowedMethods));
|
|
194
|
+
configuration.setAllowedHeaders(removeWhiteSpace(allowedHeaders));
|
|
195
|
+
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
|
196
|
+
source.registerCorsConfiguration("/**", configuration);
|
|
197
|
+
return source;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
`;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Backup original DB files and replace them with H2 config.
|
|
205
|
+
* Persists backups to .eva4j.json BEFORE writing any file so a
|
|
206
|
+
* crash mid-swap is recoverable on next run.
|
|
207
|
+
*/
|
|
208
|
+
async function swapToH2(projectDir, packageName, configManager) {
|
|
209
|
+
const backups = {};
|
|
210
|
+
|
|
211
|
+
// āā db.yaml per environment āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
212
|
+
for (const env of ENVS) {
|
|
213
|
+
const dbYamlPath = path.join(
|
|
214
|
+
projectDir, 'src', 'main', 'resources', 'parameters', env, 'db.yaml'
|
|
215
|
+
);
|
|
216
|
+
if (await fs.pathExists(dbYamlPath)) {
|
|
217
|
+
backups[`db_${env}`] = { path: dbYamlPath, content: await fs.readFile(dbYamlPath, 'utf-8') };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// āā build.gradle āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
222
|
+
const buildGradlePath = path.join(projectDir, 'build.gradle');
|
|
223
|
+
if (await fs.pathExists(buildGradlePath)) {
|
|
224
|
+
backups.buildGradle = { path: buildGradlePath, content: await fs.readFile(buildGradlePath, 'utf-8') };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// āā SecurityConfig.java āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
228
|
+
const packagePath = toPackagePath(packageName);
|
|
229
|
+
const securityConfigPath = path.join(
|
|
230
|
+
projectDir, 'src', 'main', 'java', packagePath,
|
|
231
|
+
'shared', 'infrastructure', 'configurations', 'securityConfig', 'SecurityConfig.java'
|
|
232
|
+
);
|
|
233
|
+
if (await fs.pathExists(securityConfigPath)) {
|
|
234
|
+
backups.securityConfig = { path: securityConfigPath, content: await fs.readFile(securityConfigPath, 'utf-8') };
|
|
235
|
+
} else {
|
|
236
|
+
backups.securityConfig = { path: securityConfigPath, content: null };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// āā KafkaConfig.java ā backup so it can be restored later āāāāāāāāāāāāāāāā
|
|
240
|
+
const kafkaConfigPath = path.join(
|
|
241
|
+
projectDir, 'src', 'main', 'java', packagePath,
|
|
242
|
+
'shared', 'infrastructure', 'configurations', 'kafkaConfig', 'KafkaConfig.java'
|
|
243
|
+
);
|
|
244
|
+
if (await fs.pathExists(kafkaConfigPath)) {
|
|
245
|
+
backups.kafkaConfig = { path: kafkaConfigPath, content: await fs.readFile(kafkaConfigPath, 'utf-8') };
|
|
246
|
+
}
|
|
247
|
+
// When Kafka is not installed, omit the key entirely ā restoreFromH2() iterates
|
|
248
|
+
// Object.values(backups) and would crash trying to destructure a null entry.
|
|
249
|
+
|
|
250
|
+
// Persist backups BEFORE writing any file so a crash mid-swap is recoverable
|
|
251
|
+
await configManager.saveMockBackup(backups);
|
|
252
|
+
|
|
253
|
+
// āā Write H2 versions āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
254
|
+
for (const env of ENVS) {
|
|
255
|
+
if (backups[`db_${env}`]) {
|
|
256
|
+
await fs.writeFile(backups[`db_${env}`].path, H2_DB_YAML(packageName), 'utf-8');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (backups.buildGradle) {
|
|
261
|
+
let swapped = backups.buildGradle.content.replace(
|
|
262
|
+
/^[ \t]*runtimeOnly\s+['"][^'"]+['"]\s*(?:\/\/.*)?$/m,
|
|
263
|
+
H2_GRADLE_LINE
|
|
264
|
+
);
|
|
265
|
+
// Remove spring-kafka dependencies block when Kafka is installed
|
|
266
|
+
swapped = swapped.replace(
|
|
267
|
+
/\n?[ \t]*\/\/ Kafka\n[ \t]*implementation 'org\.springframework\.kafka:spring-kafka'\n[ \t]*testImplementation 'org\.springframework\.kafka:spring-kafka-test'\n\n?[ \t]*/,
|
|
268
|
+
'\n\t'
|
|
269
|
+
);
|
|
270
|
+
await fs.writeFile(buildGradlePath, swapped, 'utf-8');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
await fs.ensureDir(path.dirname(securityConfigPath));
|
|
274
|
+
await fs.writeFile(securityConfigPath, H2_SECURITY_CONFIG(packageName), 'utf-8');
|
|
275
|
+
|
|
276
|
+
// āā Remove KafkaConfig.java (restored from backup on eva build) āāāāāāāāāā
|
|
277
|
+
if (backups.kafkaConfig) {
|
|
278
|
+
await fs.remove(kafkaConfigPath);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return backups;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Backup and swap ONLY the broker layer (Kafka ā Spring Event bus).
|
|
286
|
+
* Database config (db.yaml) and SecurityConfig.java are left untouched.
|
|
287
|
+
* Persists backups to .eva4j.json with _mockOnlyBroker = true.
|
|
288
|
+
*/
|
|
289
|
+
async function swapBrokerOnly(projectDir, packageName, configManager) {
|
|
290
|
+
const backups = {};
|
|
291
|
+
|
|
292
|
+
// āā build.gradle āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
293
|
+
const buildGradlePath = path.join(projectDir, 'build.gradle');
|
|
294
|
+
if (await fs.pathExists(buildGradlePath)) {
|
|
295
|
+
backups.buildGradle = { path: buildGradlePath, content: await fs.readFile(buildGradlePath, 'utf-8') };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// āā KafkaConfig.java āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
299
|
+
const packagePath = toPackagePath(packageName);
|
|
300
|
+
const kafkaConfigPath = path.join(
|
|
301
|
+
projectDir, 'src', 'main', 'java', packagePath,
|
|
302
|
+
'shared', 'infrastructure', 'configurations', 'kafkaConfig', 'KafkaConfig.java'
|
|
303
|
+
);
|
|
304
|
+
if (await fs.pathExists(kafkaConfigPath)) {
|
|
305
|
+
backups.kafkaConfig = { path: kafkaConfigPath, content: await fs.readFile(kafkaConfigPath, 'utf-8') };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Persist backups BEFORE writing any file so a crash mid-swap is recoverable
|
|
309
|
+
await configManager.saveMockBackup(backups, { onlyBroker: true });
|
|
310
|
+
|
|
311
|
+
// āā Remove spring-kafka from build.gradle (keep existing DB driver line) āā
|
|
312
|
+
if (backups.buildGradle) {
|
|
313
|
+
const swapped = backups.buildGradle.content.replace(
|
|
314
|
+
/\n?[ \t]*\/\/ Kafka\n[ \t]*implementation 'org\.springframework\.kafka:spring-kafka'\n[ \t]*testImplementation 'org\.springframework\.kafka:spring-kafka-test'\n\n?[ \t]*/,
|
|
315
|
+
'\n\t'
|
|
316
|
+
);
|
|
317
|
+
await fs.writeFile(buildGradlePath, swapped, 'utf-8');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// āā Remove KafkaConfig.java (restored from backup on eva build) āāāāāāāāāāā
|
|
321
|
+
if (backups.kafkaConfig) {
|
|
322
|
+
await fs.remove(kafkaConfigPath);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return backups;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Restore all files from backup stored in .eva4j.json and clear the entry.
|
|
330
|
+
*/
|
|
331
|
+
async function restoreFromH2(configManager) {
|
|
332
|
+
const backups = await configManager.popMockBackup();
|
|
333
|
+
if (!backups) return 0;
|
|
334
|
+
|
|
335
|
+
for (const { path: filePath, content } of Object.values(backups)) {
|
|
336
|
+
if (content === null) {
|
|
337
|
+
// File was created by mock ā delete it on restore
|
|
338
|
+
await fs.remove(filePath);
|
|
339
|
+
} else {
|
|
340
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return Object.keys(backups).length;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// āā Main build command āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
347
|
+
async function buildCommand(options = {}) {
|
|
348
|
+
const projectDir = process.cwd();
|
|
349
|
+
|
|
350
|
+
// āā 1. Validate project āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
351
|
+
if (!(await isEva4jProject(projectDir))) {
|
|
352
|
+
console.error(chalk.red('ā Not in an eva4j project directory'));
|
|
353
|
+
console.error(chalk.gray('Run this command inside a project created with eva4j'));
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// āā 2. Load project config āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
358
|
+
const configManager = new ConfigManager(projectDir);
|
|
359
|
+
const projectConfig = await configManager.loadProjectConfig();
|
|
360
|
+
|
|
361
|
+
if (!projectConfig) {
|
|
362
|
+
console.error(chalk.red('ā Could not load project configuration'));
|
|
363
|
+
console.error(chalk.gray('Make sure .eva4j.json exists in the project root'));
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// āā 2b. Restore mock config if a previous --mock run left files swapped āāāāāā
|
|
368
|
+
if (!options.mock && await configManager.hasMockBackup()) {
|
|
369
|
+
const isOnlyBroker = await configManager.hasMockOnlyBroker();
|
|
370
|
+
|
|
371
|
+
if (isOnlyBroker) {
|
|
372
|
+
console.log(chalk.yellow('ā ļø Detected active --only-broker mock from a previous run.'));
|
|
373
|
+
console.log(chalk.yellow(' Restoring original broker configuration before continuing...\n'));
|
|
374
|
+
} else {
|
|
375
|
+
console.log(chalk.yellow('ā ļø Detected active mock (H2) config from a previous --mock run.'));
|
|
376
|
+
console.log(chalk.yellow(' Restoring original database configuration before continuing...\n'));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Restore original files verbatim from backup (db.yaml, build.gradle, SecurityConfig, KafkaConfig)
|
|
380
|
+
// The backups contain the exact content the developer had configured ā do NOT regenerate from
|
|
381
|
+
// projectConfig defaults, which only knows the DB type and not the custom URL/user/password.
|
|
382
|
+
await restoreFromH2(configManager);
|
|
383
|
+
|
|
384
|
+
if (!isOnlyBroker) {
|
|
385
|
+
// Re-apply the correct runtimeOnly DB driver line as a safety net for build.gradle
|
|
386
|
+
await regenerateBuildGradleDbDriver(projectDir, projectConfig);
|
|
387
|
+
|
|
388
|
+
// Force-regenerate SecurityConfig from the original template
|
|
389
|
+
const { packageName: pkgNameRestore } = projectConfig;
|
|
390
|
+
const pkgPathRestore = toPackagePath(pkgNameRestore);
|
|
391
|
+
const sharedBaseRestore = path.join(projectDir, 'src', 'main', 'java', pkgPathRestore, 'shared');
|
|
392
|
+
if (await fs.pathExists(sharedBaseRestore)) {
|
|
393
|
+
const sg = new SharedGenerator({
|
|
394
|
+
packageName: pkgNameRestore,
|
|
395
|
+
packagePath: pkgPathRestore,
|
|
396
|
+
projectName: projectConfig.projectName || projectConfig.artifactId,
|
|
397
|
+
groupId: projectConfig.groupId,
|
|
398
|
+
});
|
|
399
|
+
await sg.generateConfigurations(sharedBaseRestore);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
console.log(chalk.green(' ā
Configuration restored to original.\n'));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// āā MOCK swap ā swap DB + broker config (or broker only), then run entity generation ā
|
|
407
|
+
if (options.mock) {
|
|
408
|
+
if (await configManager.hasMockBackup()) {
|
|
409
|
+
const alreadyOnlyBroker = await configManager.hasMockOnlyBroker();
|
|
410
|
+
const label = alreadyOnlyBroker ? 'broker-only mock' : 'mock (H2)';
|
|
411
|
+
console.log(chalk.yellow(`\nā” ${label} is already active. Run eva build without --mock to restore.\n`));
|
|
412
|
+
process.exit(0);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const { packageName: pkgName } = projectConfig;
|
|
416
|
+
|
|
417
|
+
if (options.onlyBroker) {
|
|
418
|
+
// āā BROKER-ONLY mode: keep database, replace broker āāāāāāāāāāāāāāāāāāā
|
|
419
|
+
console.log(chalk.blue('\nš eva build --mock --only-broker\n'));
|
|
420
|
+
console.log(chalk.gray(` Project : ${projectConfig.projectName || projectConfig.artifactId}`));
|
|
421
|
+
console.log(chalk.yellow(' Mode : switching broker to Spring Event bus (database unchanged)\n'));
|
|
422
|
+
console.log(chalk.blue('āāā Swapping broker config āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
423
|
+
|
|
424
|
+
const backups = await swapBrokerOnly(projectDir, pkgName, configManager);
|
|
425
|
+
const hasKafkaBackup = !!backups.kafkaConfig;
|
|
426
|
+
console.log(chalk.green(` ā
${Object.keys(backups).length} file(s) backed up and replaced`));
|
|
427
|
+
if (hasKafkaBackup) {
|
|
428
|
+
console.log(chalk.green(' ā
KafkaConfig.java removed (Spring Events will be used instead)'));
|
|
429
|
+
console.log(chalk.green(' ā
spring-kafka dependencies removed from build.gradle'));
|
|
430
|
+
}
|
|
431
|
+
console.log(chalk.gray(' Backup saved to .eva4j.json ā will be restored on next eva build\n'));
|
|
432
|
+
|
|
433
|
+
// āā Regenerate broker layer if system.yaml exists and Kafka was installed
|
|
434
|
+
const systemDirBo = path.join(projectDir, 'system');
|
|
435
|
+
const systemYamlPathBo = path.join(systemDirBo, 'system.yaml');
|
|
436
|
+
|
|
437
|
+
if (hasKafkaBackup && (await fs.pathExists(systemYamlPathBo))) {
|
|
438
|
+
let systemConfigBo;
|
|
439
|
+
try {
|
|
440
|
+
const content = await fs.readFile(systemYamlPathBo, 'utf-8');
|
|
441
|
+
systemConfigBo = yaml.load(content);
|
|
442
|
+
} catch (err) {
|
|
443
|
+
console.error(chalk.red('ā Failed to parse system/system.yaml:'), err.message);
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const { modules: mockModulesBo = [] } = systemConfigBo;
|
|
448
|
+
const pkgPathBo = toPackagePath(pkgName);
|
|
449
|
+
|
|
450
|
+
if (mockModulesBo.length > 0) {
|
|
451
|
+
console.log(chalk.blue('āāā Regenerating broker layer (mock) āāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
452
|
+
|
|
453
|
+
for (const mod of mockModulesBo) {
|
|
454
|
+
const sourceYaml = path.join(systemDirBo, `${mod.name}.yaml`);
|
|
455
|
+
const modulePackageName = toCamelCase(mod.name);
|
|
456
|
+
const destYaml = path.join(projectDir, 'src', 'main', 'java', pkgPathBo, modulePackageName, 'domain.yaml');
|
|
457
|
+
if (!(await fs.pathExists(sourceYaml))) {
|
|
458
|
+
console.log(chalk.yellow(` ā ļø system/${mod.name}.yaml not found ā skipping ${mod.name}`));
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const content = await fs.readFile(sourceYaml, 'utf-8');
|
|
462
|
+
await fs.ensureDir(path.dirname(destYaml));
|
|
463
|
+
await fs.writeFile(destYaml, content, 'utf-8');
|
|
464
|
+
console.log(chalk.green(` ā
${mod.name}/domain.yaml updated`));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
for (const mod of mockModulesBo) {
|
|
468
|
+
const modulePackageName = toCamelCase(mod.name);
|
|
469
|
+
const domainYamlPath = path.join(projectDir, 'src', 'main', 'java', pkgPathBo, modulePackageName, 'domain.yaml');
|
|
470
|
+
if (!(await fs.pathExists(domainYamlPath))) {
|
|
471
|
+
console.log(chalk.yellow(` ā ļø domain.yaml not found for '${mod.name}' ā skipping`));
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
console.log(chalk.cyan(`\n Regenerating broker layer for: ${mod.name}`));
|
|
475
|
+
await generateEntitiesCommand(mod.name, { force: false, brokerMode: 'mock' });
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} else if (hasKafkaBackup) {
|
|
479
|
+
console.log(chalk.yellow(' ā¹ļø No system/system.yaml found ā broker files must be regenerated manually.'));
|
|
480
|
+
console.log(chalk.yellow(' Run: eva g entities <module> (with --force if needed)'));
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
console.log();
|
|
484
|
+
console.log(chalk.yellow(' ā” Broker-only mock active. Database config unchanged.'));
|
|
485
|
+
console.log(chalk.yellow(' Run ./gradlew bootRun to start.'));
|
|
486
|
+
console.log(chalk.yellow(' Run eva build (without --mock) to restore the original broker config.\n'));
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// āā FULL mock mode: DB ā H2 + broker ā Spring Events āāāāāāāāāāāāāāāāāāāāā
|
|
491
|
+
console.log(chalk.blue('\nš eva build --mock\n'));
|
|
492
|
+
console.log(chalk.gray(` Project : ${projectConfig.projectName || projectConfig.artifactId}`));
|
|
493
|
+
console.log(chalk.yellow(' Mode : switching to H2 in-memory database + Spring Event bus\n'));
|
|
494
|
+
console.log(chalk.blue('āāā Swapping database & broker config āāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
495
|
+
|
|
496
|
+
const backups = await swapToH2(projectDir, pkgName, configManager);
|
|
497
|
+
const hasKafkaBackup = !!backups.kafkaConfig;
|
|
498
|
+
console.log(chalk.green(` ā
${Object.keys(backups).length} file(s) backed up and replaced`));
|
|
499
|
+
if (hasKafkaBackup) {
|
|
500
|
+
console.log(chalk.green(' ā
KafkaConfig.java removed (Spring Events will be used instead)'));
|
|
501
|
+
console.log(chalk.green(' ā
spring-kafka dependencies removed from build.gradle'));
|
|
502
|
+
}
|
|
503
|
+
console.log(chalk.gray(' Backup saved to .eva4j.json ā will be restored on next eva build\n'));
|
|
504
|
+
|
|
505
|
+
// āā Regenerate broker layer if system.yaml exists and Kafka was installed āā
|
|
506
|
+
const systemDir = path.join(projectDir, 'system');
|
|
507
|
+
const systemYamlPath = path.join(systemDir, 'system.yaml');
|
|
508
|
+
|
|
509
|
+
if (hasKafkaBackup && (await fs.pathExists(systemYamlPath))) {
|
|
510
|
+
let systemConfig;
|
|
511
|
+
try {
|
|
512
|
+
const content = await fs.readFile(systemYamlPath, 'utf-8');
|
|
513
|
+
systemConfig = yaml.load(content);
|
|
514
|
+
} catch (err) {
|
|
515
|
+
console.error(chalk.red('ā Failed to parse system/system.yaml:'), err.message);
|
|
516
|
+
process.exit(1);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const { modules: mockModules = [] } = systemConfig;
|
|
520
|
+
const pkgPath = toPackagePath(pkgName);
|
|
521
|
+
|
|
522
|
+
if (mockModules.length > 0) {
|
|
523
|
+
console.log(chalk.blue('āāā Regenerating broker layer (mock) āāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
524
|
+
|
|
525
|
+
// Step 3: Copy domain.yaml files
|
|
526
|
+
for (const mod of mockModules) {
|
|
527
|
+
const sourceYaml = path.join(systemDir, `${mod.name}.yaml`);
|
|
528
|
+
const modulePackageName = toCamelCase(mod.name);
|
|
529
|
+
const destYaml = path.join(projectDir, 'src', 'main', 'java', pkgPath, modulePackageName, 'domain.yaml');
|
|
530
|
+
if (!(await fs.pathExists(sourceYaml))) {
|
|
531
|
+
console.log(chalk.yellow(` ā ļø system/${mod.name}.yaml not found ā skipping ${mod.name}`));
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
const content = await fs.readFile(sourceYaml, 'utf-8');
|
|
535
|
+
await fs.ensureDir(path.dirname(destYaml));
|
|
536
|
+
await fs.writeFile(destYaml, content, 'utf-8');
|
|
537
|
+
console.log(chalk.green(` ā
${mod.name}/domain.yaml updated`));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Step 4: Regenerate entities with mock broker
|
|
541
|
+
for (const mod of mockModules) {
|
|
542
|
+
const modulePackageName = toCamelCase(mod.name);
|
|
543
|
+
const domainYamlPath = path.join(projectDir, 'src', 'main', 'java', pkgPath, modulePackageName, 'domain.yaml');
|
|
544
|
+
if (!(await fs.pathExists(domainYamlPath))) {
|
|
545
|
+
console.log(chalk.yellow(` ā ļø domain.yaml not found for '${mod.name}' ā skipping`));
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
console.log(chalk.cyan(`\n Regenerating broker layer for: ${mod.name}`));
|
|
549
|
+
await generateEntitiesCommand(mod.name, { force: false, brokerMode: 'mock' });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
} else if (hasKafkaBackup) {
|
|
553
|
+
console.log(chalk.yellow(' ā¹ļø No system/system.yaml found ā broker files must be regenerated manually.'));
|
|
554
|
+
console.log(chalk.yellow(' Run: eva g entities <module> (with --force if needed)'));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
console.log();
|
|
558
|
+
console.log(chalk.yellow(' ā” Mock mode active. Run ./gradlew bootRun to start.'));
|
|
559
|
+
console.log(chalk.yellow(' Run eva build (without --mock) to restore the original config.\n'));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const { packageName } = projectConfig;
|
|
564
|
+
const packagePath = toPackagePath(packageName);
|
|
565
|
+
|
|
566
|
+
// āā 3. Read system/system.yaml āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
567
|
+
const systemDir = path.join(projectDir, 'system');
|
|
568
|
+
const systemYamlPath = path.join(systemDir, 'system.yaml');
|
|
569
|
+
|
|
570
|
+
if (!(await fs.pathExists(systemYamlPath))) {
|
|
571
|
+
console.error(chalk.red('ā system/system.yaml not found'));
|
|
572
|
+
console.error(chalk.gray('Create system/system.yaml first with module definitions'));
|
|
573
|
+
process.exit(1);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
let systemConfig;
|
|
577
|
+
try {
|
|
578
|
+
const content = await fs.readFile(systemYamlPath, 'utf-8');
|
|
579
|
+
systemConfig = yaml.load(content);
|
|
580
|
+
} catch (err) {
|
|
581
|
+
console.error(chalk.red('ā Failed to parse system/system.yaml:'), err.message);
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const { modules = [], messaging } = systemConfig;
|
|
586
|
+
|
|
587
|
+
if (!modules.length) {
|
|
588
|
+
console.log(chalk.yellow('ā ļø No modules defined in system/system.yaml'));
|
|
589
|
+
process.exit(0);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
console.log(chalk.blue('\nšļø eva build\n'));
|
|
593
|
+
console.log(chalk.gray(` Project : ${projectConfig.projectName || projectConfig.artifactId}`));
|
|
594
|
+
console.log(chalk.gray(` Modules : ${modules.map(m => m.name).join(', ')}`));
|
|
595
|
+
console.log();
|
|
596
|
+
|
|
597
|
+
// āā STEP 1: Create modules āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
598
|
+
console.log(chalk.blue('āāā Step 1: Creating modules āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
599
|
+
|
|
600
|
+
for (const mod of modules) {
|
|
601
|
+
const modulePackageName = toCamelCase(mod.name);
|
|
602
|
+
|
|
603
|
+
if (await configManager.moduleExists(modulePackageName)) {
|
|
604
|
+
console.log(chalk.gray(` ā ${mod.name} ā already exists, skipping`));
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const moduleDir = path.join(projectDir, 'src', 'main', 'java', packagePath, modulePackageName);
|
|
609
|
+
if (await fs.pathExists(moduleDir)) {
|
|
610
|
+
console.log(chalk.gray(` ā ${mod.name} ā directory already exists, skipping`));
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
console.log(chalk.cyan(` ā Adding module: ${mod.name}`));
|
|
615
|
+
await addModuleCommand(mod.name, {});
|
|
616
|
+
await configManager.loadProjectConfig();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
console.log();
|
|
620
|
+
|
|
621
|
+
// āā STEP 2: Install broker client āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
622
|
+
console.log(chalk.blue('āāā Step 2: Installing broker client āāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
623
|
+
|
|
624
|
+
const brokerEnabled = messaging && messaging.enabled === true;
|
|
625
|
+
const broker = messaging && messaging.broker;
|
|
626
|
+
|
|
627
|
+
if (!brokerEnabled || !broker) {
|
|
628
|
+
console.log(chalk.gray(' ā No messaging configured, skipping broker install'));
|
|
629
|
+
} else if (broker === 'kafka') {
|
|
630
|
+
if (await configManager.featureExists('kafka')) {
|
|
631
|
+
console.log(chalk.gray(' ā kafka-client ā already installed, skipping'));
|
|
632
|
+
} else {
|
|
633
|
+
console.log(chalk.cyan(' ā Installing kafka-client'));
|
|
634
|
+
await addKafkaClientCommand();
|
|
635
|
+
}
|
|
636
|
+
} else {
|
|
637
|
+
console.log(chalk.yellow(` ā ļø Broker '${broker}' is not supported by eva build (only kafka is supported)`));
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
console.log();
|
|
641
|
+
|
|
642
|
+
// āā STEP 3: Copy domain.yaml files āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
643
|
+
console.log(chalk.blue('āāā Step 3: Copying domain.yaml files āāāāāāāāāāāāāāāāāāāāāāā'));
|
|
644
|
+
|
|
645
|
+
for (const mod of modules) {
|
|
646
|
+
const sourceYaml = path.join(systemDir, `${mod.name}.yaml`);
|
|
647
|
+
const modulePackageName = toCamelCase(mod.name);
|
|
648
|
+
const destYaml = path.join(
|
|
649
|
+
projectDir, 'src', 'main', 'java', packagePath, modulePackageName, 'domain.yaml'
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
if (!(await fs.pathExists(sourceYaml))) {
|
|
653
|
+
console.log(chalk.yellow(` ā ļø system/${mod.name}.yaml not found ā skipping ${mod.name}`));
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const content = await fs.readFile(sourceYaml, 'utf-8');
|
|
658
|
+
await fs.ensureDir(path.dirname(destYaml));
|
|
659
|
+
await fs.writeFile(destYaml, content, 'utf-8');
|
|
660
|
+
console.log(chalk.green(` ā
${mod.name}/domain.yaml updated`));
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
console.log();
|
|
664
|
+
|
|
665
|
+
// āā STEP 4: Generate entities āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
666
|
+
console.log(chalk.blue('āāā Step 4: Generating entities āāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
667
|
+
|
|
668
|
+
const generateOptions = { force: options.force || false };
|
|
669
|
+
|
|
670
|
+
for (const mod of modules) {
|
|
671
|
+
const modulePackageName = toCamelCase(mod.name);
|
|
672
|
+
const domainYamlPath = path.join(
|
|
673
|
+
projectDir, 'src', 'main', 'java', packagePath, modulePackageName, 'domain.yaml'
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
if (!(await fs.pathExists(domainYamlPath))) {
|
|
677
|
+
console.log(chalk.yellow(` ā ļø domain.yaml not found for '${mod.name}' ā skipping entity generation`));
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
console.log(chalk.cyan(`\n Generating entities for: ${mod.name}`));
|
|
682
|
+
await generateEntitiesCommand(mod.name, { ...generateOptions, skipPostman: true });
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
console.log();
|
|
686
|
+
|
|
687
|
+
// āā STEP 5: Generate unified Postman collection āāāāāāāāāāāāāāāāāāāāāāāāā
|
|
688
|
+
console.log(chalk.blue('āāā Step 5: Generating unified Postman collection āāāāāāāāāāāā'));
|
|
689
|
+
|
|
690
|
+
try {
|
|
691
|
+
const collectionPath = await generateUnifiedPostmanCollection({
|
|
692
|
+
projectDir,
|
|
693
|
+
systemDir,
|
|
694
|
+
packageName,
|
|
695
|
+
systemConfig,
|
|
696
|
+
projectConfig,
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
if (collectionPath) {
|
|
700
|
+
const relPath = path.relative(projectDir, collectionPath);
|
|
701
|
+
console.log(chalk.green(` ā
${relPath}`));
|
|
702
|
+
console.log(chalk.cyan('\n š” Import this collection into Postman to test all your API endpoints!'));
|
|
703
|
+
} else {
|
|
704
|
+
console.log(chalk.gray(' ā No modules with domain definitions found ā skipping'));
|
|
705
|
+
}
|
|
706
|
+
} catch (err) {
|
|
707
|
+
console.log(chalk.yellow(` ā ļø Could not generate Postman collection: ${err.message}`));
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
console.log();
|
|
711
|
+
console.log(chalk.green('ā
eva build completed successfully\n'));
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
module.exports = buildCommand;
|