eva4j 1.0.16 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +220 -5
- package/DOMAIN_YAML_GUIDE.md +188 -3
- package/FUTURE_FEATURES.md +33 -52
- package/QUICK_REFERENCE.md +8 -4
- package/bin/eva4j.js +70 -2
- package/config/defaults.json +1 -0
- package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
- package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
- package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
- package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
- package/docs/commands/EVALUATE_SYSTEM.md +290 -10
- package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
- package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
- package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
- package/docs/commands/INDEX.md +27 -3
- package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
- package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
- package/docs/prototype/system/RISKS.md +277 -0
- package/docs/prototype/system/customers.yaml +133 -0
- package/docs/prototype/system/inventory.yaml +109 -0
- package/docs/prototype/system/notifications.yaml +131 -0
- package/docs/prototype/system/orders.yaml +241 -0
- package/docs/prototype/system/payments.yaml +256 -0
- package/docs/prototype/system/products.yaml +168 -0
- package/docs/prototype/system/system.yaml +269 -0
- package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
- package/examples/domain-events.yaml +26 -0
- package/examples/domain-read-models.yaml +113 -0
- package/examples/system/customer.yaml +89 -0
- package/examples/system/orders.yaml +119 -0
- package/examples/system/product.yaml +27 -0
- package/examples/system/system.yaml +80 -0
- package/package.json +1 -1
- package/read-model-spec.md +664 -0
- package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
- package/src/agents/design-gap-analyst.agent.md +383 -0
- package/src/agents/design-reviewer-temporal.agent.md +412 -0
- package/src/agents/design-reviewer.agent.md +34 -5
- package/src/agents/implement-use-cases.prompt.md +179 -0
- package/src/agents/ux-gap-analyst.agent.md +412 -0
- package/src/commands/add-rabbitmq-client.js +261 -0
- package/src/commands/add-temporal-client.js +22 -2
- package/src/commands/build.js +267 -11
- package/src/commands/evaluate-system.js +700 -13
- package/src/commands/generate-entities.js +560 -24
- 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-rabbitmq-event.js +665 -0
- package/src/commands/generate-rabbitmq-listener.js +205 -0
- package/src/commands/generate-record.js +2 -2
- package/src/commands/generate-resource.js +4 -1
- package/src/commands/generate-temporal-activity.js +970 -33
- package/src/commands/generate-temporal-flow.js +98 -38
- package/src/commands/generate-temporal-system.js +708 -0
- package/src/commands/generate-usecase.js +4 -1
- package/src/skills/build-system-yaml/SKILL.md +343 -2
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +253 -26
- package/src/skills/build-system-yaml/references/module-spec.md +90 -9
- package/src/skills/build-system-yaml/references/system-yaml-spec.md +36 -0
- package/src/skills/build-temporal-system/SKILL.md +752 -0
- package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
- package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
- package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
- package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
- package/src/skills/implement-use-case/SKILL.md +350 -0
- package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
- package/src/skills/requirements-elicitation/SKILL.md +228 -0
- package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
- package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
- package/src/utils/bounded-context-diagram.js +844 -0
- package/src/utils/config-manager.js +4 -2
- package/src/utils/domain-validator.js +495 -17
- package/src/utils/naming.js +20 -0
- package/src/utils/system-validator.js +169 -11
- package/src/utils/system-yaml-parser.js +318 -0
- package/src/utils/temporal-validator.js +497 -0
- package/src/utils/validator.js +3 -1
- package/src/utils/yaml-to-entity.js +281 -9
- 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/DomainEventHandler.java.ejs +116 -22
- package/templates/aggregate/JpaAggregateRoot.java.ejs +4 -4
- package/templates/aggregate/JpaEntity.java.ejs +2 -2
- package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
- package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
- package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
- package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
- package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
- package/templates/base/root/AGENTS.md.ejs +1 -1
- package/templates/crud/DeleteCommandHandler.java.ejs +19 -1
- package/templates/crud/EndpointsController.java.ejs +1 -1
- package/templates/crud/ScaffoldCommand.java.ejs +5 -2
- package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
- package/templates/crud/ScaffoldQuery.java.ejs +5 -2
- package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
- package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
- package/templates/crud/UpdateCommandHandler.java.ejs +53 -2
- package/templates/evaluate/report.html.ejs +1447 -90
- package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
- package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
- package/templates/ports/PortAclMapper.java.ejs +35 -0
- package/templates/ports/PortFeignAdapter.java.ejs +7 -22
- package/templates/ports/PortFeignClient.java.ejs +4 -0
- package/templates/ports/PortResponseDto.java.ejs +1 -1
- package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
- package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
- package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
- package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
- package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
- package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
- 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 +13 -0
- package/templates/read-model/ReadModelKafkaListener.java.ejs +64 -0
- package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
- package/templates/read-model/ReadModelRepository.java.ejs +42 -0
- package/templates/read-model/ReadModelRepositoryImpl.java.ejs +85 -0
- package/templates/read-model/ReadModelSyncHandler.java.ejs +54 -0
- package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
- package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
- package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
- package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
- package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
- package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
- package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/NestedType.java.ejs +12 -0
- package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
- package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
- package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
- package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
- package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
- package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
- package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
- package/COMMAND_EVALUATION.md +0 -911
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const yaml = require('js-yaml');
|
|
7
|
+
const ConfigManager = require('../utils/config-manager');
|
|
8
|
+
const { isEva4jProject } = require('../utils/validator');
|
|
9
|
+
const { toPackagePath, toPascalCase, toCamelCase, toKebabCase } = require('../utils/naming');
|
|
10
|
+
const { renderAndWrite, renderTemplate } = require('../utils/template-engine');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate RabbitMQ listener methods in a module's infrastructure
|
|
14
|
+
* @param {string} moduleName - Name of the module
|
|
15
|
+
*/
|
|
16
|
+
async function generateRabbitMQListenerCommand(moduleName) {
|
|
17
|
+
const projectDir = process.cwd();
|
|
18
|
+
|
|
19
|
+
// Validate eva4j project
|
|
20
|
+
if (!(await isEva4jProject(projectDir))) {
|
|
21
|
+
console.error(chalk.red('❌ Not in an eva4j project directory'));
|
|
22
|
+
console.error(chalk.gray(' Run this command from the root of an eva4j project'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check if RabbitMQ is installed
|
|
27
|
+
const configManager = new ConfigManager(projectDir);
|
|
28
|
+
if (!(await configManager.featureExists('rabbitmq'))) {
|
|
29
|
+
console.error(chalk.red('❌ RabbitMQ client is not installed in this project'));
|
|
30
|
+
console.error(chalk.gray(' Run: eva4j add rabbitmq-client'));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Load project configuration
|
|
35
|
+
const projectConfig = await configManager.loadProjectConfig();
|
|
36
|
+
const { packageName, projectName } = projectConfig;
|
|
37
|
+
const packagePath = toPackagePath(packageName);
|
|
38
|
+
|
|
39
|
+
// Normalise module name to camelCase
|
|
40
|
+
moduleName = toCamelCase(moduleName);
|
|
41
|
+
|
|
42
|
+
// Validate module exists
|
|
43
|
+
if (!(await configManager.moduleExists(moduleName))) {
|
|
44
|
+
console.error(chalk.red(`❌ Module '${moduleName}' not found in project`));
|
|
45
|
+
console.error(chalk.gray(' Available modules:'));
|
|
46
|
+
const modules = projectConfig.modules || [];
|
|
47
|
+
modules.forEach(mod => console.error(chalk.gray(` - ${mod}`)));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Read available queues from rabbitmq.yaml
|
|
52
|
+
const queues = await getAvailableQueues(projectDir);
|
|
53
|
+
|
|
54
|
+
if (queues.length === 0) {
|
|
55
|
+
console.error(chalk.red('❌ No queues found in rabbitmq.yaml'));
|
|
56
|
+
console.error(chalk.gray(' Add queues using: eva4j generate rabbitmq-event <module> <event-name>'));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Prompt for queue selection (multiple)
|
|
61
|
+
const { selectedQueues } = await inquirer.prompt([
|
|
62
|
+
{
|
|
63
|
+
type: 'checkbox',
|
|
64
|
+
name: 'selectedQueues',
|
|
65
|
+
message: 'Select queues to listen to (use space to select, enter to confirm):',
|
|
66
|
+
choices: queues.map(q => ({
|
|
67
|
+
name: `${q.key} (${q.value})`,
|
|
68
|
+
value: q.key,
|
|
69
|
+
checked: false
|
|
70
|
+
})),
|
|
71
|
+
validate: (answer) => {
|
|
72
|
+
if (answer.length === 0) {
|
|
73
|
+
return 'You must select at least one queue';
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
const spinner = ora('Generating RabbitMQ listeners...').start();
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const generatedListeners = [];
|
|
84
|
+
|
|
85
|
+
for (const queueKey of selectedQueues) {
|
|
86
|
+
const listenerContext = buildQueueContext(packageName, moduleName, queueKey, queues);
|
|
87
|
+
|
|
88
|
+
const listenerPath = path.join(
|
|
89
|
+
projectDir,
|
|
90
|
+
'src',
|
|
91
|
+
'main',
|
|
92
|
+
'java',
|
|
93
|
+
packagePath,
|
|
94
|
+
moduleName,
|
|
95
|
+
'infrastructure',
|
|
96
|
+
'rabbitListener',
|
|
97
|
+
`${listenerContext.listenerClassName}.java`
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Check if listener already exists
|
|
101
|
+
if (await fs.pathExists(listenerPath)) {
|
|
102
|
+
console.log(chalk.yellow(` ⚠ ${listenerContext.listenerClassName}.java already exists, skipping...`));
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
spinner.text = `Generating ${listenerContext.listenerClassName}...`;
|
|
107
|
+
|
|
108
|
+
const templatePath = path.join(__dirname, '..', '..', 'templates', 'rabbitmq-listener', 'RabbitListenerSimple.java.ejs');
|
|
109
|
+
await renderAndWrite(templatePath, listenerPath, listenerContext);
|
|
110
|
+
|
|
111
|
+
generatedListeners.push(listenerContext.listenerClassName);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (generatedListeners.length === 0) {
|
|
115
|
+
spinner.warn(chalk.yellow('No new listeners were generated (all already exist)'));
|
|
116
|
+
} else {
|
|
117
|
+
spinner.succeed(chalk.green(`✨ ${generatedListeners.length} RabbitMQ listener(s) generated successfully!`));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Display generated components
|
|
121
|
+
console.log(chalk.blue('\n📦 Generated components:'));
|
|
122
|
+
generatedListeners.forEach(className => {
|
|
123
|
+
console.log(chalk.gray(` ├── ${moduleName}/infrastructure/rabbitListener/${className}.java`));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (generatedListeners.length > 0) {
|
|
127
|
+
console.log(chalk.blue('\n✅ Listeners configured:'));
|
|
128
|
+
selectedQueues.forEach(queue => {
|
|
129
|
+
const className = generateListenerClassName(queue, moduleName);
|
|
130
|
+
console.log(chalk.gray(` ├── ${className} - listening to: ${queue}`));
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
console.log(chalk.yellow('\n💡 Next steps:'));
|
|
134
|
+
console.log(chalk.gray(' 1. Implement event processing logic in each listener\'s handle() method'));
|
|
135
|
+
console.log(chalk.gray(' 2. Consider creating use cases to handle events via UseCaseMediator'));
|
|
136
|
+
console.log(chalk.gray(' 3. Test your listeners with RabbitMQ producer'));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
} catch (error) {
|
|
140
|
+
spinner.fail(chalk.red('Failed to generate RabbitMQ listener'));
|
|
141
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
142
|
+
if (process.env.DEBUG) {
|
|
143
|
+
console.error(error.stack);
|
|
144
|
+
}
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Read available queues from rabbitmq.yaml
|
|
151
|
+
*/
|
|
152
|
+
async function getAvailableQueues(projectDir) {
|
|
153
|
+
const rabbitYmlPath = path.join(projectDir, 'src', 'main', 'resources', 'parameters', 'local', 'rabbitmq.yaml');
|
|
154
|
+
|
|
155
|
+
if (!(await fs.pathExists(rabbitYmlPath))) {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const rabbitContent = await fs.readFile(rabbitYmlPath, 'utf8');
|
|
160
|
+
const rabbitConfig = yaml.load(rabbitContent);
|
|
161
|
+
|
|
162
|
+
if (!rabbitConfig.queues || Object.keys(rabbitConfig.queues).length === 0) {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return Object.entries(rabbitConfig.queues).map(([key, value]) => ({
|
|
167
|
+
key,
|
|
168
|
+
value
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Build context object for a queue
|
|
174
|
+
*/
|
|
175
|
+
function buildQueueContext(packageName, moduleName, queueKey, allQueues) {
|
|
176
|
+
const queue = allQueues.find(q => q.key === queueKey);
|
|
177
|
+
const topicValue = queue.value;
|
|
178
|
+
|
|
179
|
+
const listenerClassName = generateListenerClassName(queueKey, moduleName);
|
|
180
|
+
const listenerBeanName = toCamelCase(listenerClassName);
|
|
181
|
+
const topicVariableName = toCamelCase(queueKey);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
packageName,
|
|
185
|
+
moduleName,
|
|
186
|
+
topicNameKebab: queueKey,
|
|
187
|
+
topicValue,
|
|
188
|
+
topicSpringProperty: `\${queues.${queueKey}}`,
|
|
189
|
+
topicVariableName,
|
|
190
|
+
listenerClassName,
|
|
191
|
+
listenerBeanName
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Generate listener class name from queue key and module name
|
|
197
|
+
* Example: order-placed + notification → NotificationOrderPlacedListener
|
|
198
|
+
*/
|
|
199
|
+
function generateListenerClassName(queueKey, moduleName) {
|
|
200
|
+
const modulePrefix = toPascalCase(moduleName);
|
|
201
|
+
const topicName = toPascalCase(queueKey);
|
|
202
|
+
return `${modulePrefix}${topicName}Listener`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = generateRabbitMQListenerCommand;
|
|
@@ -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`));
|