eventmodeler 0.2.1 → 0.2.3
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 +51 -19
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.js +26 -0
- package/dist/lib/format.d.ts +3 -0
- package/dist/lib/format.js +11 -0
- package/dist/projection.js +29 -35
- package/dist/slices/list-chapters/index.d.ts +2 -1
- package/dist/slices/list-chapters/index.js +10 -11
- package/dist/slices/list-commands/index.d.ts +2 -1
- package/dist/slices/list-commands/index.js +9 -10
- package/dist/slices/list-events/index.d.ts +2 -1
- package/dist/slices/list-events/index.js +33 -13
- package/dist/slices/list-slices/index.d.ts +2 -1
- package/dist/slices/list-slices/index.js +9 -10
- 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 +44 -9
- package/dist/slices/show-aggregate-completeness/index.d.ts +3 -2
- package/dist/slices/show-aggregate-completeness/index.js +60 -9
- package/dist/slices/show-chapter/index.d.ts +2 -1
- package/dist/slices/show-chapter/index.js +9 -9
- package/dist/slices/show-command/index.d.ts +2 -1
- package/dist/slices/show-command/index.js +52 -9
- package/dist/slices/show-completeness/index.d.ts +3 -1
- package/dist/slices/show-completeness/index.js +225 -32
- package/dist/slices/show-event/index.d.ts +2 -1
- package/dist/slices/show-event/index.js +41 -9
- 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 +162 -9
- 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,7 +26,7 @@ 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';
|
|
@@ -53,7 +60,8 @@ COMMANDS:
|
|
|
53
60
|
show event <name> Show detailed XML view of an event
|
|
54
61
|
show command <name> Show detailed XML view of a command
|
|
55
62
|
show chapter <name> Show chapter with its slices
|
|
56
|
-
show completeness <
|
|
63
|
+
show completeness <name> Show field mapping completeness for any element
|
|
64
|
+
show model-completeness Show completeness of all flows in the model
|
|
57
65
|
show aggregate-completeness <name>
|
|
58
66
|
Show if events in aggregate have the ID field
|
|
59
67
|
show actor <name> Show actor with its screens
|
|
@@ -85,7 +93,14 @@ COMMANDS:
|
|
|
85
93
|
|
|
86
94
|
OPTIONS:
|
|
87
95
|
-f, --file <path> Path to .eventmodel file (default: auto-detect)
|
|
96
|
+
--format <xml|json> Output format (default: xml, or from config)
|
|
88
97
|
-h, --help Show this help message
|
|
98
|
+
-v, --version Show version number
|
|
99
|
+
|
|
100
|
+
CONFIGURATION:
|
|
101
|
+
Set default output format via:
|
|
102
|
+
- Environment variable: EVENTMODELER_FORMAT=json
|
|
103
|
+
- Config file: ~/.eventmodeler/config.json with {"format": "json"}
|
|
89
104
|
|
|
90
105
|
EXAMPLES:
|
|
91
106
|
eventmodeler list slices
|
|
@@ -95,25 +110,38 @@ EXAMPLES:
|
|
|
95
110
|
eventmodeler add field --event "OrderPlaced" --json '{"name": "orderId", "type": "UUID"}'
|
|
96
111
|
eventmodeler remove field --event "OrderPlaced" --field "orderId"
|
|
97
112
|
eventmodeler show completeness "OrderSummary"
|
|
113
|
+
eventmodeler show model-completeness
|
|
98
114
|
eventmodeler map fields --flow "OrderPlaced→OrderSummary" --json '[{"from": "total", "to": "totalAmount"}]'
|
|
99
115
|
eventmodeler update field --read-model "OrderSummary" --field "notes" --optional true
|
|
100
116
|
`);
|
|
101
117
|
}
|
|
102
118
|
async function main() {
|
|
103
119
|
let fileArg = null;
|
|
120
|
+
let formatArg = null;
|
|
104
121
|
const filteredArgs = [];
|
|
105
122
|
for (let i = 0; i < args.length; i++) {
|
|
106
123
|
if (args[i] === '-f' || args[i] === '--file') {
|
|
107
124
|
fileArg = args[++i];
|
|
108
125
|
}
|
|
126
|
+
else if (args[i] === '--format') {
|
|
127
|
+
formatArg = args[++i];
|
|
128
|
+
}
|
|
109
129
|
else if (args[i] === '-h' || args[i] === '--help') {
|
|
110
130
|
printHelp();
|
|
111
131
|
process.exit(0);
|
|
112
132
|
}
|
|
133
|
+
else if (args[i] === '-v' || args[i] === '--version') {
|
|
134
|
+
console.log(`eventmodeler ${VERSION}`);
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
113
137
|
else {
|
|
114
138
|
filteredArgs.push(args[i]);
|
|
115
139
|
}
|
|
116
140
|
}
|
|
141
|
+
// Determine output format
|
|
142
|
+
const format = formatArg === 'json' ? 'json'
|
|
143
|
+
: formatArg === 'xml' ? 'xml'
|
|
144
|
+
: getDefaultFormat();
|
|
117
145
|
const command = filteredArgs[0];
|
|
118
146
|
const subcommand = filteredArgs[1];
|
|
119
147
|
const target = filteredArgs[2];
|
|
@@ -136,22 +164,22 @@ async function main() {
|
|
|
136
164
|
case 'list':
|
|
137
165
|
switch (subcommand) {
|
|
138
166
|
case 'slices':
|
|
139
|
-
listSlices(model);
|
|
167
|
+
listSlices(model, format);
|
|
140
168
|
break;
|
|
141
169
|
case 'events':
|
|
142
|
-
listEvents(model);
|
|
170
|
+
listEvents(model, format);
|
|
143
171
|
break;
|
|
144
172
|
case 'commands':
|
|
145
|
-
listCommands(model);
|
|
173
|
+
listCommands(model, format);
|
|
146
174
|
break;
|
|
147
175
|
case 'chapters':
|
|
148
|
-
listChapters(model);
|
|
176
|
+
listChapters(model, format);
|
|
149
177
|
break;
|
|
150
178
|
case 'aggregates':
|
|
151
|
-
listAggregates(model);
|
|
179
|
+
listAggregates(model, format);
|
|
152
180
|
break;
|
|
153
181
|
case 'actors':
|
|
154
|
-
listActors(model);
|
|
182
|
+
listActors(model, format);
|
|
155
183
|
break;
|
|
156
184
|
default:
|
|
157
185
|
console.error(`Unknown list target: ${subcommand}`);
|
|
@@ -166,53 +194,57 @@ async function main() {
|
|
|
166
194
|
console.error('Usage: eventmodeler show slice <name>');
|
|
167
195
|
process.exit(1);
|
|
168
196
|
}
|
|
169
|
-
showSlice(model, target);
|
|
197
|
+
showSlice(model, target, format);
|
|
170
198
|
break;
|
|
171
199
|
case 'event':
|
|
172
200
|
if (!target) {
|
|
173
201
|
console.error('Usage: eventmodeler show event <name>');
|
|
174
202
|
process.exit(1);
|
|
175
203
|
}
|
|
176
|
-
showEvent(model, target);
|
|
204
|
+
showEvent(model, target, format);
|
|
177
205
|
break;
|
|
178
206
|
case 'command':
|
|
179
207
|
if (!target) {
|
|
180
208
|
console.error('Usage: eventmodeler show command <name>');
|
|
181
209
|
process.exit(1);
|
|
182
210
|
}
|
|
183
|
-
showCommand(model, target);
|
|
211
|
+
showCommand(model, target, format);
|
|
184
212
|
break;
|
|
185
213
|
case 'chapter':
|
|
186
214
|
if (!target) {
|
|
187
215
|
console.error('Usage: eventmodeler show chapter <name>');
|
|
188
216
|
process.exit(1);
|
|
189
217
|
}
|
|
190
|
-
showChapter(model, target);
|
|
218
|
+
showChapter(model, target, format);
|
|
191
219
|
break;
|
|
192
220
|
case 'completeness':
|
|
193
221
|
if (!target) {
|
|
194
|
-
console.error('Usage: eventmodeler show completeness <
|
|
222
|
+
console.error('Usage: eventmodeler show completeness <element-name>');
|
|
223
|
+
console.error('Searches: commands, events, read models, screens, processors');
|
|
195
224
|
process.exit(1);
|
|
196
225
|
}
|
|
197
|
-
showCompleteness(model, target);
|
|
226
|
+
showCompleteness(model, target, format);
|
|
227
|
+
break;
|
|
228
|
+
case 'model-completeness':
|
|
229
|
+
showModelCompleteness(model, format);
|
|
198
230
|
break;
|
|
199
231
|
case 'aggregate-completeness':
|
|
200
232
|
if (!target) {
|
|
201
233
|
console.error('Usage: eventmodeler show aggregate-completeness <aggregate-name>');
|
|
202
234
|
process.exit(1);
|
|
203
235
|
}
|
|
204
|
-
showAggregateCompleteness(model, target);
|
|
236
|
+
showAggregateCompleteness(model, target, format);
|
|
205
237
|
break;
|
|
206
238
|
case 'actor':
|
|
207
239
|
if (!target) {
|
|
208
240
|
console.error('Usage: eventmodeler show actor <actor-name>');
|
|
209
241
|
process.exit(1);
|
|
210
242
|
}
|
|
211
|
-
showActor(model, target);
|
|
243
|
+
showActor(model, target, format);
|
|
212
244
|
break;
|
|
213
245
|
default:
|
|
214
246
|
console.error(`Unknown show target: ${subcommand}`);
|
|
215
|
-
console.error('Valid targets: slice, event, command, chapter, completeness, aggregate-completeness, actor');
|
|
247
|
+
console.error('Valid targets: slice, event, command, chapter, completeness, model-completeness, aggregate-completeness, actor');
|
|
216
248
|
process.exit(1);
|
|
217
249
|
}
|
|
218
250
|
break;
|
|
@@ -221,7 +253,7 @@ async function main() {
|
|
|
221
253
|
console.error('Usage: eventmodeler search <term>');
|
|
222
254
|
process.exit(1);
|
|
223
255
|
}
|
|
224
|
-
search(model, subcommand);
|
|
256
|
+
search(model, subcommand, format);
|
|
225
257
|
break;
|
|
226
258
|
case 'mark':
|
|
227
259
|
if (!subcommand || !target) {
|
|
@@ -232,7 +264,7 @@ async function main() {
|
|
|
232
264
|
markSliceStatus(model, filePath, subcommand, target);
|
|
233
265
|
break;
|
|
234
266
|
case 'summary':
|
|
235
|
-
showModelSummary(model);
|
|
267
|
+
showModelSummary(model, format);
|
|
236
268
|
break;
|
|
237
269
|
case 'export':
|
|
238
270
|
switch (subcommand) {
|
|
@@ -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,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
|
+
}
|
package/dist/projection.js
CHANGED
|
@@ -826,57 +826,51 @@ function applyEvent(model, event) {
|
|
|
826
826
|
break;
|
|
827
827
|
}
|
|
828
828
|
}
|
|
829
|
+
// Merge propagated fields with existing fields:
|
|
830
|
+
// - If a field with the same name exists, update its subfields
|
|
831
|
+
// - If it doesn't exist, add as new field
|
|
832
|
+
function mergeFieldsInto(existingFields, propagatedFields) {
|
|
833
|
+
for (const propagatedField of propagatedFields) {
|
|
834
|
+
const existingIdx = existingFields.findIndex(f => f.name === propagatedField.name);
|
|
835
|
+
if (existingIdx !== -1) {
|
|
836
|
+
// Field exists - update its subfields
|
|
837
|
+
existingFields[existingIdx] = {
|
|
838
|
+
...existingFields[existingIdx],
|
|
839
|
+
subfields: propagatedField.subfields,
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
// New field - add it
|
|
844
|
+
existingFields.push(propagatedField);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
829
848
|
function handleFieldPropagation(model, event) {
|
|
830
849
|
const fields = event.fields;
|
|
831
850
|
// Determine target based on event type
|
|
832
851
|
if ('eventStickyId' in event) {
|
|
833
852
|
const evt = model.events.get(event.eventStickyId);
|
|
834
|
-
if (evt)
|
|
835
|
-
|
|
836
|
-
if (!evt.fields.some(f => f.name === field.name)) {
|
|
837
|
-
evt.fields.push(field);
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
}
|
|
853
|
+
if (evt)
|
|
854
|
+
mergeFieldsInto(evt.fields, fields);
|
|
841
855
|
}
|
|
842
856
|
else if ('commandStickyId' in event) {
|
|
843
857
|
const cmd = model.commands.get(event.commandStickyId);
|
|
844
|
-
if (cmd)
|
|
845
|
-
|
|
846
|
-
if (!cmd.fields.some(f => f.name === field.name)) {
|
|
847
|
-
cmd.fields.push(field);
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
}
|
|
858
|
+
if (cmd)
|
|
859
|
+
mergeFieldsInto(cmd.fields, fields);
|
|
851
860
|
}
|
|
852
861
|
else if ('readModelStickyId' in event) {
|
|
853
862
|
const rm = model.readModels.get(event.readModelStickyId);
|
|
854
|
-
if (rm)
|
|
855
|
-
|
|
856
|
-
if (!rm.fields.some(f => f.name === field.name)) {
|
|
857
|
-
rm.fields.push(field);
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
}
|
|
863
|
+
if (rm)
|
|
864
|
+
mergeFieldsInto(rm.fields, fields);
|
|
861
865
|
}
|
|
862
866
|
else if ('screenId' in event) {
|
|
863
867
|
const scr = model.screens.get(event.screenId);
|
|
864
|
-
if (scr)
|
|
865
|
-
|
|
866
|
-
if (!scr.fields.some(f => f.name === field.name)) {
|
|
867
|
-
scr.fields.push(field);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}
|
|
868
|
+
if (scr)
|
|
869
|
+
mergeFieldsInto(scr.fields, fields);
|
|
871
870
|
}
|
|
872
871
|
else if ('processorId' in event) {
|
|
873
872
|
const proc = model.processors.get(event.processorId);
|
|
874
|
-
if (proc)
|
|
875
|
-
|
|
876
|
-
if (!proc.fields.some(f => f.name === field.name)) {
|
|
877
|
-
proc.fields.push(field);
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
873
|
+
if (proc)
|
|
874
|
+
mergeFieldsInto(proc.fields, fields);
|
|
881
875
|
}
|
|
882
876
|
}
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
.replace(/&/g, '&')
|
|
4
|
-
.replace(/</g, '<')
|
|
5
|
-
.replace(/>/g, '>')
|
|
6
|
-
.replace(/"/g, '"')
|
|
7
|
-
.replace(/'/g, ''');
|
|
8
|
-
}
|
|
9
|
-
export function listChapters(model) {
|
|
1
|
+
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
2
|
+
export function listChapters(model, format) {
|
|
10
3
|
const chapters = [...model.chapters.values()];
|
|
4
|
+
// Sort by x position (left to right on timeline)
|
|
5
|
+
const sorted = [...chapters].sort((a, b) => a.position.x - b.position.x);
|
|
6
|
+
if (format === 'json') {
|
|
7
|
+
outputJson({
|
|
8
|
+
chapters: sorted.map(ch => ({ name: ch.name }))
|
|
9
|
+
});
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
11
12
|
if (chapters.length === 0) {
|
|
12
13
|
console.log('<chapters/>');
|
|
13
14
|
return;
|
|
14
15
|
}
|
|
15
|
-
// Sort by x position (left to right on timeline)
|
|
16
|
-
const sorted = [...chapters].sort((a, b) => a.position.x - b.position.x);
|
|
17
16
|
console.log('<chapters>');
|
|
18
17
|
for (const chapter of sorted) {
|
|
19
18
|
console.log(` <chapter name="${escapeXml(chapter.name)}"/>`);
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
.replace(/&/g, '&')
|
|
4
|
-
.replace(/</g, '<')
|
|
5
|
-
.replace(/>/g, '>')
|
|
6
|
-
.replace(/"/g, '"')
|
|
7
|
-
.replace(/'/g, ''');
|
|
8
|
-
}
|
|
9
|
-
export function listCommands(model) {
|
|
1
|
+
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
2
|
+
export function listCommands(model, format) {
|
|
10
3
|
const commands = [...model.commands.values()];
|
|
4
|
+
const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name));
|
|
5
|
+
if (format === 'json') {
|
|
6
|
+
outputJson({
|
|
7
|
+
commands: sorted.map(cmd => ({ name: cmd.name, fields: cmd.fields.length }))
|
|
8
|
+
});
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
11
|
if (commands.length === 0) {
|
|
12
12
|
console.log('<commands/>');
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
|
-
const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name));
|
|
16
15
|
console.log('<commands>');
|
|
17
16
|
for (const cmd of sorted) {
|
|
18
17
|
console.log(` <command name="${escapeXml(cmd.name)}" fields="${cmd.fields.length}"/>`);
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
return str
|
|
3
|
-
.replace(/&/g, '&')
|
|
4
|
-
.replace(/</g, '<')
|
|
5
|
-
.replace(/>/g, '>')
|
|
6
|
-
.replace(/"/g, '"')
|
|
7
|
-
.replace(/'/g, ''');
|
|
8
|
-
}
|
|
1
|
+
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
9
2
|
// Find which aggregate an event belongs to (center point inside aggregate bounds)
|
|
10
3
|
function findAggregateForEvent(model, event) {
|
|
11
4
|
const centerX = event.position.x + event.width / 2;
|
|
@@ -37,12 +30,8 @@ function findSliceForEvent(model, event) {
|
|
|
37
30
|
}
|
|
38
31
|
return null;
|
|
39
32
|
}
|
|
40
|
-
export function listEvents(model) {
|
|
33
|
+
export function listEvents(model, format) {
|
|
41
34
|
const events = [...model.events.values()];
|
|
42
|
-
if (events.length === 0) {
|
|
43
|
-
console.log('<events/>');
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
35
|
// Sort: originals first, then copies; alphabetically within each group
|
|
47
36
|
const sorted = [...events].sort((a, b) => {
|
|
48
37
|
// Originals before copies
|
|
@@ -53,6 +42,37 @@ export function listEvents(model) {
|
|
|
53
42
|
// Alphabetically by name
|
|
54
43
|
return a.name.localeCompare(b.name);
|
|
55
44
|
});
|
|
45
|
+
if (format === 'json') {
|
|
46
|
+
outputJson({
|
|
47
|
+
events: sorted.map(evt => {
|
|
48
|
+
const aggregate = findAggregateForEvent(model, evt);
|
|
49
|
+
const result = {
|
|
50
|
+
name: evt.name,
|
|
51
|
+
fields: evt.fields.length,
|
|
52
|
+
};
|
|
53
|
+
if (aggregate)
|
|
54
|
+
result.aggregate = aggregate.name;
|
|
55
|
+
if (evt.originalNodeId) {
|
|
56
|
+
result.linkedCopy = true;
|
|
57
|
+
const original = model.events.get(evt.originalNodeId);
|
|
58
|
+
const originSlice = original ? findSliceForEvent(model, original) : null;
|
|
59
|
+
if (originSlice)
|
|
60
|
+
result.originSlice = originSlice;
|
|
61
|
+
}
|
|
62
|
+
else if (evt.canonicalId) {
|
|
63
|
+
const copyCount = [...model.events.values()].filter(e => e.canonicalId === evt.canonicalId && e.originalNodeId).length;
|
|
64
|
+
if (copyCount > 0)
|
|
65
|
+
result.copies = copyCount;
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
})
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (events.length === 0) {
|
|
73
|
+
console.log('<events/>');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
56
76
|
console.log('<events>');
|
|
57
77
|
for (const evt of sorted) {
|
|
58
78
|
const aggregate = findAggregateForEvent(model, evt);
|