eventmodeler 0.2.1 → 0.2.4
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/README.md +175 -0
- package/dist/index.js +114 -19
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.js +26 -0
- package/dist/lib/element-lookup.d.ts +47 -0
- package/dist/lib/element-lookup.js +86 -0
- package/dist/lib/format.d.ts +3 -0
- package/dist/lib/format.js +11 -0
- package/dist/lib/slice-utils.d.ts +83 -0
- package/dist/lib/slice-utils.js +135 -0
- package/dist/projection.js +161 -35
- package/dist/slices/add-field/index.js +4 -33
- package/dist/slices/add-scenario/index.js +7 -74
- package/dist/slices/create-automation-slice/index.d.ts +2 -0
- package/dist/slices/create-automation-slice/index.js +217 -0
- package/dist/slices/create-flow/index.d.ts +2 -0
- package/dist/slices/create-flow/index.js +177 -0
- package/dist/slices/create-state-change-slice/index.d.ts +2 -0
- package/dist/slices/create-state-change-slice/index.js +239 -0
- package/dist/slices/create-state-view-slice/index.d.ts +2 -0
- package/dist/slices/create-state-view-slice/index.js +120 -0
- package/dist/slices/list-chapters/index.d.ts +2 -1
- package/dist/slices/list-chapters/index.js +11 -12
- package/dist/slices/list-commands/index.d.ts +2 -1
- package/dist/slices/list-commands/index.js +10 -11
- package/dist/slices/list-events/index.d.ts +2 -1
- package/dist/slices/list-events/index.js +36 -15
- package/dist/slices/list-slices/index.d.ts +2 -1
- package/dist/slices/list-slices/index.js +10 -11
- package/dist/slices/mark-slice-status/index.js +2 -11
- package/dist/slices/remove-field/index.js +4 -33
- package/dist/slices/remove-scenario/index.js +45 -11
- package/dist/slices/search/index.d.ts +2 -1
- package/dist/slices/search/index.js +148 -21
- package/dist/slices/show-actor/index.d.ts +3 -2
- package/dist/slices/show-actor/index.js +46 -20
- package/dist/slices/show-aggregate-completeness/index.d.ts +3 -2
- package/dist/slices/show-aggregate-completeness/index.js +62 -20
- package/dist/slices/show-chapter/index.d.ts +2 -1
- package/dist/slices/show-chapter/index.js +14 -22
- package/dist/slices/show-command/index.d.ts +2 -1
- package/dist/slices/show-command/index.js +54 -19
- package/dist/slices/show-completeness/index.d.ts +3 -1
- package/dist/slices/show-completeness/index.js +313 -31
- package/dist/slices/show-event/index.d.ts +2 -1
- package/dist/slices/show-event/index.js +44 -20
- package/dist/slices/show-model-summary/index.d.ts +2 -1
- package/dist/slices/show-model-summary/index.js +18 -9
- package/dist/slices/show-slice/index.d.ts +2 -1
- package/dist/slices/show-slice/index.js +174 -24
- package/dist/slices/update-field/index.js +4 -33
- package/package.json +5 -3
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# eventmodeler
|
|
2
|
+
|
|
3
|
+
CLI tool for interacting with [Event Model](https://eventmodeling.org) files. Query, update, and export event models from the terminal.
|
|
4
|
+
|
|
5
|
+
Works with `.eventmodel` files created by the [Event Modeling App](https://www.eventmodeling.app).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g eventmodeler
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Open Event Modeling app in browser
|
|
17
|
+
eventmodeler
|
|
18
|
+
|
|
19
|
+
# List all slices
|
|
20
|
+
eventmodeler list slices
|
|
21
|
+
|
|
22
|
+
# Show details of a slice
|
|
23
|
+
eventmodeler show slice "Place Order"
|
|
24
|
+
|
|
25
|
+
# Search for elements
|
|
26
|
+
eventmodeler search "order"
|
|
27
|
+
|
|
28
|
+
# Get JSON output instead of XML
|
|
29
|
+
eventmodeler list slices --format json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
### List Commands
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
eventmodeler list slices # List all slices with status
|
|
38
|
+
eventmodeler list events # List all events
|
|
39
|
+
eventmodeler list commands # List all commands
|
|
40
|
+
eventmodeler list chapters # List all chapters
|
|
41
|
+
eventmodeler list aggregates # List all aggregates
|
|
42
|
+
eventmodeler list actors # List all actors
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Show Commands
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
eventmodeler show slice <name> # Show detailed view of a slice
|
|
49
|
+
eventmodeler show event <name> # Show detailed view of an event
|
|
50
|
+
eventmodeler show command <name> # Show detailed view of a command
|
|
51
|
+
eventmodeler show chapter <name> # Show chapter with its slices
|
|
52
|
+
eventmodeler show actor <name> # Show actor with its screens
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Completeness Commands
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
eventmodeler show completeness <name> # Show field mapping completeness for any element
|
|
59
|
+
eventmodeler show model-completeness # Show completeness of all flows in the model
|
|
60
|
+
eventmodeler show aggregate-completeness <name> # Check if events have the aggregate ID field
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Search
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
eventmodeler search <term> # Search for entities by name
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Modify Commands
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Mark slice status
|
|
73
|
+
eventmodeler mark "Place Order" done
|
|
74
|
+
eventmodeler mark "Cancel Order" in-progress
|
|
75
|
+
|
|
76
|
+
# Add scenario to a slice
|
|
77
|
+
eventmodeler add scenario --slice "Place Order" --json '{"name": "Happy path", ...}'
|
|
78
|
+
|
|
79
|
+
# Add field to an entity
|
|
80
|
+
eventmodeler add field --event "OrderPlaced" --json '{"name": "orderId", "type": "UUID"}'
|
|
81
|
+
|
|
82
|
+
# Remove field
|
|
83
|
+
eventmodeler remove field --event "OrderPlaced" --field "legacyId"
|
|
84
|
+
|
|
85
|
+
# Update field properties
|
|
86
|
+
eventmodeler update field --read-model "OrderSummary" --field "notes" --optional true
|
|
87
|
+
|
|
88
|
+
# Map fields between elements
|
|
89
|
+
eventmodeler map fields --flow "OrderPlaced→OrderSummary" --json '[{"from": "total", "to": "totalAmount"}]'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Export
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
eventmodeler summary # Show model summary statistics
|
|
96
|
+
eventmodeler export json # Export entire model as JSON
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Output Format
|
|
100
|
+
|
|
101
|
+
By default, output is in XML format (optimized for AI agents). Use `--format json` for JSON output:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
eventmodeler list slices --format json
|
|
105
|
+
eventmodeler show slice "Place Order" --format json
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Setting Default Format
|
|
109
|
+
|
|
110
|
+
Set your preferred default format via:
|
|
111
|
+
|
|
112
|
+
**Environment variable:**
|
|
113
|
+
```bash
|
|
114
|
+
export EVENTMODELER_FORMAT=json
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Config file** (`~/.eventmodeler/config.json`):
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"format": "json"
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Priority: CLI flag > environment variable > config file > default (xml)
|
|
125
|
+
|
|
126
|
+
## Options
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
-f, --file <path> Path to .eventmodel file (default: auto-detect)
|
|
130
|
+
--format <xml|json> Output format (default: xml)
|
|
131
|
+
-h, --help Show help message
|
|
132
|
+
-v, --version Show version number
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Use Cases
|
|
136
|
+
|
|
137
|
+
### For AI Agents
|
|
138
|
+
|
|
139
|
+
The CLI is designed to work well with AI coding assistants. The XML output format provides structured, readable data that AI agents can easily parse and understand:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
eventmodeler show slice "Place Order"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### For Code Generators
|
|
146
|
+
|
|
147
|
+
Use JSON output to build code generators that read your event model:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
eventmodeler list events --format json | your-codegen-tool
|
|
151
|
+
eventmodeler show slice "Place Order" --format json
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### For CI/CD
|
|
155
|
+
|
|
156
|
+
Check model completeness in your pipeline:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
eventmodeler show model-completeness --format json
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Requirements
|
|
163
|
+
|
|
164
|
+
- Node.js >= 18.0.0
|
|
165
|
+
- A `.eventmodel` file in the current directory (or specify with `-f`)
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT
|
|
170
|
+
|
|
171
|
+
## Links
|
|
172
|
+
|
|
173
|
+
- [Event Modeling App](https://www.eventmodeling.app)
|
|
174
|
+
- [Event Modeling](https://eventmodeling.org)
|
|
175
|
+
- [GitHub Repository](https://github.com/theoema/event-modeler)
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { findEventModelFile, loadModel } from './lib/file-loader.js';
|
|
6
|
+
import { getDefaultFormat } from './lib/config.js';
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
10
|
+
const VERSION = packageJson.version;
|
|
4
11
|
// Import slices
|
|
5
12
|
import { listSlices } from './slices/list-slices/index.js';
|
|
6
13
|
import { listEvents } from './slices/list-events/index.js';
|
|
@@ -19,11 +26,15 @@ import { addScenario } from './slices/add-scenario/index.js';
|
|
|
19
26
|
import { addField } from './slices/add-field/index.js';
|
|
20
27
|
import { removeScenario } from './slices/remove-scenario/index.js';
|
|
21
28
|
import { removeField } from './slices/remove-field/index.js';
|
|
22
|
-
import { showCompleteness } from './slices/show-completeness/index.js';
|
|
29
|
+
import { showCompleteness, showModelCompleteness } from './slices/show-completeness/index.js';
|
|
23
30
|
import { mapFields } from './slices/map-fields/index.js';
|
|
24
31
|
import { updateField } from './slices/update-field/index.js';
|
|
25
32
|
import { showAggregateCompleteness, listAggregates } from './slices/show-aggregate-completeness/index.js';
|
|
26
33
|
import { showActor, listActors } from './slices/show-actor/index.js';
|
|
34
|
+
import { createStateChangeSlice } from './slices/create-state-change-slice/index.js';
|
|
35
|
+
import { createAutomationSlice } from './slices/create-automation-slice/index.js';
|
|
36
|
+
import { createStateViewSlice } from './slices/create-state-view-slice/index.js';
|
|
37
|
+
import { createFlow } from './slices/create-flow/index.js';
|
|
27
38
|
const args = process.argv.slice(2);
|
|
28
39
|
function getNamedArg(argList, ...names) {
|
|
29
40
|
for (let i = 0; i < argList.length; i++) {
|
|
@@ -53,7 +64,8 @@ COMMANDS:
|
|
|
53
64
|
show event <name> Show detailed XML view of an event
|
|
54
65
|
show command <name> Show detailed XML view of a command
|
|
55
66
|
show chapter <name> Show chapter with its slices
|
|
56
|
-
show completeness <
|
|
67
|
+
show completeness <name> Show field mapping completeness for any element
|
|
68
|
+
show model-completeness Show completeness of all flows in the model
|
|
57
69
|
show aggregate-completeness <name>
|
|
58
70
|
Show if events in aggregate have the ID field
|
|
59
71
|
show actor <name> Show actor with its screens
|
|
@@ -79,13 +91,29 @@ COMMANDS:
|
|
|
79
91
|
update field --command|--event|--read-model <name> --field <name> [--optional true|false] [--generated true|false]
|
|
80
92
|
Update field properties
|
|
81
93
|
|
|
94
|
+
create state-change-slice --xml <data>
|
|
95
|
+
Create a state-change slice (Screen → Command → Event)
|
|
96
|
+
create automation-slice --xml <data>
|
|
97
|
+
Create an automation slice (Processor → Command → Event)
|
|
98
|
+
create state-view-slice --xml <data>
|
|
99
|
+
Create a state-view slice (Read Model)
|
|
100
|
+
create flow --from <source> --to <target>
|
|
101
|
+
Create a flow between elements (Event→ReadModel, ReadModel→Screen/Processor)
|
|
102
|
+
|
|
82
103
|
summary Show model summary statistics
|
|
83
104
|
|
|
84
105
|
export json Export entire model as JSON
|
|
85
106
|
|
|
86
107
|
OPTIONS:
|
|
87
108
|
-f, --file <path> Path to .eventmodel file (default: auto-detect)
|
|
109
|
+
--format <xml|json> Output format (default: xml, or from config)
|
|
88
110
|
-h, --help Show this help message
|
|
111
|
+
-v, --version Show version number
|
|
112
|
+
|
|
113
|
+
CONFIGURATION:
|
|
114
|
+
Set default output format via:
|
|
115
|
+
- Environment variable: EVENTMODELER_FORMAT=json
|
|
116
|
+
- Config file: ~/.eventmodeler/config.json with {"format": "json"}
|
|
89
117
|
|
|
90
118
|
EXAMPLES:
|
|
91
119
|
eventmodeler list slices
|
|
@@ -95,25 +123,38 @@ EXAMPLES:
|
|
|
95
123
|
eventmodeler add field --event "OrderPlaced" --json '{"name": "orderId", "type": "UUID"}'
|
|
96
124
|
eventmodeler remove field --event "OrderPlaced" --field "orderId"
|
|
97
125
|
eventmodeler show completeness "OrderSummary"
|
|
126
|
+
eventmodeler show model-completeness
|
|
98
127
|
eventmodeler map fields --flow "OrderPlaced→OrderSummary" --json '[{"from": "total", "to": "totalAmount"}]'
|
|
99
128
|
eventmodeler update field --read-model "OrderSummary" --field "notes" --optional true
|
|
100
129
|
`);
|
|
101
130
|
}
|
|
102
131
|
async function main() {
|
|
103
132
|
let fileArg = null;
|
|
133
|
+
let formatArg = null;
|
|
104
134
|
const filteredArgs = [];
|
|
105
135
|
for (let i = 0; i < args.length; i++) {
|
|
106
136
|
if (args[i] === '-f' || args[i] === '--file') {
|
|
107
137
|
fileArg = args[++i];
|
|
108
138
|
}
|
|
139
|
+
else if (args[i] === '--format') {
|
|
140
|
+
formatArg = args[++i];
|
|
141
|
+
}
|
|
109
142
|
else if (args[i] === '-h' || args[i] === '--help') {
|
|
110
143
|
printHelp();
|
|
111
144
|
process.exit(0);
|
|
112
145
|
}
|
|
146
|
+
else if (args[i] === '-v' || args[i] === '--version') {
|
|
147
|
+
console.log(`eventmodeler ${VERSION}`);
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
113
150
|
else {
|
|
114
151
|
filteredArgs.push(args[i]);
|
|
115
152
|
}
|
|
116
153
|
}
|
|
154
|
+
// Determine output format
|
|
155
|
+
const format = formatArg === 'json' ? 'json'
|
|
156
|
+
: formatArg === 'xml' ? 'xml'
|
|
157
|
+
: getDefaultFormat();
|
|
117
158
|
const command = filteredArgs[0];
|
|
118
159
|
const subcommand = filteredArgs[1];
|
|
119
160
|
const target = filteredArgs[2];
|
|
@@ -136,22 +177,22 @@ async function main() {
|
|
|
136
177
|
case 'list':
|
|
137
178
|
switch (subcommand) {
|
|
138
179
|
case 'slices':
|
|
139
|
-
listSlices(model);
|
|
180
|
+
listSlices(model, format);
|
|
140
181
|
break;
|
|
141
182
|
case 'events':
|
|
142
|
-
listEvents(model);
|
|
183
|
+
listEvents(model, format);
|
|
143
184
|
break;
|
|
144
185
|
case 'commands':
|
|
145
|
-
listCommands(model);
|
|
186
|
+
listCommands(model, format);
|
|
146
187
|
break;
|
|
147
188
|
case 'chapters':
|
|
148
|
-
listChapters(model);
|
|
189
|
+
listChapters(model, format);
|
|
149
190
|
break;
|
|
150
191
|
case 'aggregates':
|
|
151
|
-
listAggregates(model);
|
|
192
|
+
listAggregates(model, format);
|
|
152
193
|
break;
|
|
153
194
|
case 'actors':
|
|
154
|
-
listActors(model);
|
|
195
|
+
listActors(model, format);
|
|
155
196
|
break;
|
|
156
197
|
default:
|
|
157
198
|
console.error(`Unknown list target: ${subcommand}`);
|
|
@@ -166,53 +207,57 @@ async function main() {
|
|
|
166
207
|
console.error('Usage: eventmodeler show slice <name>');
|
|
167
208
|
process.exit(1);
|
|
168
209
|
}
|
|
169
|
-
showSlice(model, target);
|
|
210
|
+
showSlice(model, target, format);
|
|
170
211
|
break;
|
|
171
212
|
case 'event':
|
|
172
213
|
if (!target) {
|
|
173
214
|
console.error('Usage: eventmodeler show event <name>');
|
|
174
215
|
process.exit(1);
|
|
175
216
|
}
|
|
176
|
-
showEvent(model, target);
|
|
217
|
+
showEvent(model, target, format);
|
|
177
218
|
break;
|
|
178
219
|
case 'command':
|
|
179
220
|
if (!target) {
|
|
180
221
|
console.error('Usage: eventmodeler show command <name>');
|
|
181
222
|
process.exit(1);
|
|
182
223
|
}
|
|
183
|
-
showCommand(model, target);
|
|
224
|
+
showCommand(model, target, format);
|
|
184
225
|
break;
|
|
185
226
|
case 'chapter':
|
|
186
227
|
if (!target) {
|
|
187
228
|
console.error('Usage: eventmodeler show chapter <name>');
|
|
188
229
|
process.exit(1);
|
|
189
230
|
}
|
|
190
|
-
showChapter(model, target);
|
|
231
|
+
showChapter(model, target, format);
|
|
191
232
|
break;
|
|
192
233
|
case 'completeness':
|
|
193
234
|
if (!target) {
|
|
194
|
-
console.error('Usage: eventmodeler show completeness <
|
|
235
|
+
console.error('Usage: eventmodeler show completeness <element-name>');
|
|
236
|
+
console.error('Searches: commands, events, read models, screens, processors');
|
|
195
237
|
process.exit(1);
|
|
196
238
|
}
|
|
197
|
-
showCompleteness(model, target);
|
|
239
|
+
showCompleteness(model, target, format);
|
|
240
|
+
break;
|
|
241
|
+
case 'model-completeness':
|
|
242
|
+
showModelCompleteness(model, format);
|
|
198
243
|
break;
|
|
199
244
|
case 'aggregate-completeness':
|
|
200
245
|
if (!target) {
|
|
201
246
|
console.error('Usage: eventmodeler show aggregate-completeness <aggregate-name>');
|
|
202
247
|
process.exit(1);
|
|
203
248
|
}
|
|
204
|
-
showAggregateCompleteness(model, target);
|
|
249
|
+
showAggregateCompleteness(model, target, format);
|
|
205
250
|
break;
|
|
206
251
|
case 'actor':
|
|
207
252
|
if (!target) {
|
|
208
253
|
console.error('Usage: eventmodeler show actor <actor-name>');
|
|
209
254
|
process.exit(1);
|
|
210
255
|
}
|
|
211
|
-
showActor(model, target);
|
|
256
|
+
showActor(model, target, format);
|
|
212
257
|
break;
|
|
213
258
|
default:
|
|
214
259
|
console.error(`Unknown show target: ${subcommand}`);
|
|
215
|
-
console.error('Valid targets: slice, event, command, chapter, completeness, aggregate-completeness, actor');
|
|
260
|
+
console.error('Valid targets: slice, event, command, chapter, completeness, model-completeness, aggregate-completeness, actor');
|
|
216
261
|
process.exit(1);
|
|
217
262
|
}
|
|
218
263
|
break;
|
|
@@ -221,7 +266,7 @@ async function main() {
|
|
|
221
266
|
console.error('Usage: eventmodeler search <term>');
|
|
222
267
|
process.exit(1);
|
|
223
268
|
}
|
|
224
|
-
search(model, subcommand);
|
|
269
|
+
search(model, subcommand, format);
|
|
225
270
|
break;
|
|
226
271
|
case 'mark':
|
|
227
272
|
if (!subcommand || !target) {
|
|
@@ -232,7 +277,7 @@ async function main() {
|
|
|
232
277
|
markSliceStatus(model, filePath, subcommand, target);
|
|
233
278
|
break;
|
|
234
279
|
case 'summary':
|
|
235
|
-
showModelSummary(model);
|
|
280
|
+
showModelSummary(model, format);
|
|
236
281
|
break;
|
|
237
282
|
case 'export':
|
|
238
283
|
switch (subcommand) {
|
|
@@ -381,6 +426,56 @@ async function main() {
|
|
|
381
426
|
process.exit(1);
|
|
382
427
|
}
|
|
383
428
|
break;
|
|
429
|
+
case 'create':
|
|
430
|
+
switch (subcommand) {
|
|
431
|
+
case 'state-change-slice': {
|
|
432
|
+
const xmlArg = getNamedArg(filteredArgs, '--xml');
|
|
433
|
+
if (!xmlArg) {
|
|
434
|
+
console.error('Error: --xml is required');
|
|
435
|
+
console.error('Usage: eventmodeler create state-change-slice --xml \'<state-change-slice>...</state-change-slice>\'');
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
createStateChangeSlice(model, filePath, xmlArg);
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
case 'automation-slice': {
|
|
442
|
+
const xmlArg = getNamedArg(filteredArgs, '--xml');
|
|
443
|
+
if (!xmlArg) {
|
|
444
|
+
console.error('Error: --xml is required');
|
|
445
|
+
console.error('Usage: eventmodeler create automation-slice --xml \'<automation-slice>...</automation-slice>\'');
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
createAutomationSlice(model, filePath, xmlArg);
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
case 'state-view-slice': {
|
|
452
|
+
const xmlArg = getNamedArg(filteredArgs, '--xml');
|
|
453
|
+
if (!xmlArg) {
|
|
454
|
+
console.error('Error: --xml is required');
|
|
455
|
+
console.error('Usage: eventmodeler create state-view-slice --xml \'<state-view-slice>...</state-view-slice>\'');
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
createStateViewSlice(model, filePath, xmlArg);
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
case 'flow': {
|
|
462
|
+
const fromArg = getNamedArg(filteredArgs, '--from');
|
|
463
|
+
const toArg = getNamedArg(filteredArgs, '--to');
|
|
464
|
+
if (!fromArg || !toArg) {
|
|
465
|
+
console.error('Error: --from and --to are required');
|
|
466
|
+
console.error('Usage: eventmodeler create flow --from <source> --to <target>');
|
|
467
|
+
console.error('Valid flows: Event→ReadModel, ReadModel→Screen, ReadModel→Processor');
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
createFlow(model, filePath, fromArg, toArg);
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
default:
|
|
474
|
+
console.error(`Unknown create target: ${subcommand}`);
|
|
475
|
+
console.error('Valid targets: state-change-slice, automation-slice, state-view-slice, flow');
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
break;
|
|
384
479
|
default:
|
|
385
480
|
console.error(`Unknown command: ${command}`);
|
|
386
481
|
printHelp();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
export function getDefaultFormat() {
|
|
5
|
+
// 1. Check environment variable
|
|
6
|
+
const envFormat = process.env.EVENTMODELER_FORMAT;
|
|
7
|
+
if (envFormat === 'json')
|
|
8
|
+
return 'json';
|
|
9
|
+
if (envFormat === 'xml')
|
|
10
|
+
return 'xml';
|
|
11
|
+
// 2. Check config file
|
|
12
|
+
try {
|
|
13
|
+
const configPath = path.join(os.homedir(), '.eventmodeler', 'config.json');
|
|
14
|
+
if (fs.existsSync(configPath)) {
|
|
15
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
16
|
+
if (config.format === 'json' || config.format === 'xml') {
|
|
17
|
+
return config.format;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Ignore config file errors
|
|
23
|
+
}
|
|
24
|
+
// 3. Default to XML (better for AI agents)
|
|
25
|
+
return 'xml';
|
|
26
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Element lookup utilities for CLI commands.
|
|
3
|
+
* Provides exact-match lookup with UUID disambiguation for duplicate names.
|
|
4
|
+
*/
|
|
5
|
+
export type LookupResult<T> = {
|
|
6
|
+
success: true;
|
|
7
|
+
element: T;
|
|
8
|
+
} | {
|
|
9
|
+
success: false;
|
|
10
|
+
error: 'not_found' | 'ambiguous';
|
|
11
|
+
matches: T[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Find an element by exact name (case-insensitive) or UUID.
|
|
15
|
+
* - If search starts with "id:", treats rest as UUID/UUID prefix
|
|
16
|
+
* - Otherwise, performs case-insensitive exact name match
|
|
17
|
+
* - Returns error if multiple elements have the same name
|
|
18
|
+
*/
|
|
19
|
+
export declare function findElement<T extends {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
}>(elements: Map<string, T> | T[], search: string): LookupResult<T>;
|
|
23
|
+
/**
|
|
24
|
+
* Format an element name with its ID for display
|
|
25
|
+
*/
|
|
26
|
+
export declare function formatElementWithId<T extends {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
}>(element: T, truncateId?: boolean): string;
|
|
30
|
+
/**
|
|
31
|
+
* Print error message for lookup failures and exit
|
|
32
|
+
*/
|
|
33
|
+
export declare function handleLookupError<T extends {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
}>(search: string, elementType: string, result: {
|
|
37
|
+
success: false;
|
|
38
|
+
error: 'not_found' | 'ambiguous';
|
|
39
|
+
matches: T[];
|
|
40
|
+
}, allElements: Map<string, T> | T[]): never;
|
|
41
|
+
/**
|
|
42
|
+
* Convenience function: find element or exit with error
|
|
43
|
+
*/
|
|
44
|
+
export declare function findElementOrExit<T extends {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
}>(elements: Map<string, T> | T[], search: string, elementType: string): T;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Element lookup utilities for CLI commands.
|
|
3
|
+
* Provides exact-match lookup with UUID disambiguation for duplicate names.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Find an element by exact name (case-insensitive) or UUID.
|
|
7
|
+
* - If search starts with "id:", treats rest as UUID/UUID prefix
|
|
8
|
+
* - Otherwise, performs case-insensitive exact name match
|
|
9
|
+
* - Returns error if multiple elements have the same name
|
|
10
|
+
*/
|
|
11
|
+
export function findElement(elements, search) {
|
|
12
|
+
const elementArray = Array.isArray(elements) ? elements : [...elements.values()];
|
|
13
|
+
// Check for UUID lookup (id:prefix or full UUID format)
|
|
14
|
+
if (search.startsWith('id:')) {
|
|
15
|
+
const idSearch = search.slice(3).toLowerCase();
|
|
16
|
+
const match = elementArray.find(e => e.id.toLowerCase().startsWith(idSearch));
|
|
17
|
+
if (match) {
|
|
18
|
+
return { success: true, element: match };
|
|
19
|
+
}
|
|
20
|
+
return { success: false, error: 'not_found', matches: [] };
|
|
21
|
+
}
|
|
22
|
+
// Check if the search looks like a UUID (contains dashes in UUID pattern)
|
|
23
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
24
|
+
if (uuidPattern.test(search)) {
|
|
25
|
+
const match = elementArray.find(e => e.id.toLowerCase() === search.toLowerCase());
|
|
26
|
+
if (match) {
|
|
27
|
+
return { success: true, element: match };
|
|
28
|
+
}
|
|
29
|
+
return { success: false, error: 'not_found', matches: [] };
|
|
30
|
+
}
|
|
31
|
+
// Case-insensitive exact name match
|
|
32
|
+
const searchLower = search.toLowerCase();
|
|
33
|
+
const matches = elementArray.filter(e => e.name.toLowerCase() === searchLower);
|
|
34
|
+
if (matches.length === 1) {
|
|
35
|
+
return { success: true, element: matches[0] };
|
|
36
|
+
}
|
|
37
|
+
if (matches.length > 1) {
|
|
38
|
+
return { success: false, error: 'ambiguous', matches };
|
|
39
|
+
}
|
|
40
|
+
return { success: false, error: 'not_found', matches: [] };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Format an element name with its ID for display
|
|
44
|
+
*/
|
|
45
|
+
export function formatElementWithId(element, truncateId = true) {
|
|
46
|
+
const id = truncateId ? element.id.slice(0, 8) : element.id;
|
|
47
|
+
return `"${element.name}" (id: ${id})`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Print error message for lookup failures and exit
|
|
51
|
+
*/
|
|
52
|
+
export function handleLookupError(search, elementType, result, allElements) {
|
|
53
|
+
const elementArray = Array.isArray(allElements) ? allElements : [...allElements.values()];
|
|
54
|
+
if (result.error === 'ambiguous') {
|
|
55
|
+
console.error(`Error: Multiple ${elementType}s found with name "${search}"`);
|
|
56
|
+
console.error('Please specify using the element ID:');
|
|
57
|
+
for (const el of result.matches) {
|
|
58
|
+
console.error(` - ${formatElementWithId(el, false)}`);
|
|
59
|
+
}
|
|
60
|
+
console.error('');
|
|
61
|
+
console.error(`Usage: eventmodeler <command> "id:${result.matches[0].id.slice(0, 8)}"`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.error(`Error: ${elementType.charAt(0).toUpperCase() + elementType.slice(1)} not found: "${search}"`);
|
|
65
|
+
if (elementArray.length > 0) {
|
|
66
|
+
console.error(`Available ${elementType}s:`);
|
|
67
|
+
for (const el of elementArray) {
|
|
68
|
+
console.error(` - ${formatElementWithId(el)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.error(`No ${elementType}s exist in the model.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Convenience function: find element or exit with error
|
|
79
|
+
*/
|
|
80
|
+
export function findElementOrExit(elements, search, elementType) {
|
|
81
|
+
const result = findElement(elements, search);
|
|
82
|
+
if (!result.success) {
|
|
83
|
+
handleLookupError(search, elementType, result, elements);
|
|
84
|
+
}
|
|
85
|
+
return result.element;
|
|
86
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function escapeXml(str) {
|
|
2
|
+
return str
|
|
3
|
+
.replace(/&/g, '&')
|
|
4
|
+
.replace(/</g, '<')
|
|
5
|
+
.replace(/>/g, '>')
|
|
6
|
+
.replace(/"/g, '"')
|
|
7
|
+
.replace(/'/g, ''');
|
|
8
|
+
}
|
|
9
|
+
export function outputJson(data) {
|
|
10
|
+
console.log(JSON.stringify(data, null, 2));
|
|
11
|
+
}
|