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.
Files changed (52) hide show
  1. package/README.md +175 -0
  2. package/dist/index.js +114 -19
  3. package/dist/lib/config.d.ts +2 -0
  4. package/dist/lib/config.js +26 -0
  5. package/dist/lib/element-lookup.d.ts +47 -0
  6. package/dist/lib/element-lookup.js +86 -0
  7. package/dist/lib/format.d.ts +3 -0
  8. package/dist/lib/format.js +11 -0
  9. package/dist/lib/slice-utils.d.ts +83 -0
  10. package/dist/lib/slice-utils.js +135 -0
  11. package/dist/projection.js +161 -35
  12. package/dist/slices/add-field/index.js +4 -33
  13. package/dist/slices/add-scenario/index.js +7 -74
  14. package/dist/slices/create-automation-slice/index.d.ts +2 -0
  15. package/dist/slices/create-automation-slice/index.js +217 -0
  16. package/dist/slices/create-flow/index.d.ts +2 -0
  17. package/dist/slices/create-flow/index.js +177 -0
  18. package/dist/slices/create-state-change-slice/index.d.ts +2 -0
  19. package/dist/slices/create-state-change-slice/index.js +239 -0
  20. package/dist/slices/create-state-view-slice/index.d.ts +2 -0
  21. package/dist/slices/create-state-view-slice/index.js +120 -0
  22. package/dist/slices/list-chapters/index.d.ts +2 -1
  23. package/dist/slices/list-chapters/index.js +11 -12
  24. package/dist/slices/list-commands/index.d.ts +2 -1
  25. package/dist/slices/list-commands/index.js +10 -11
  26. package/dist/slices/list-events/index.d.ts +2 -1
  27. package/dist/slices/list-events/index.js +36 -15
  28. package/dist/slices/list-slices/index.d.ts +2 -1
  29. package/dist/slices/list-slices/index.js +10 -11
  30. package/dist/slices/mark-slice-status/index.js +2 -11
  31. package/dist/slices/remove-field/index.js +4 -33
  32. package/dist/slices/remove-scenario/index.js +45 -11
  33. package/dist/slices/search/index.d.ts +2 -1
  34. package/dist/slices/search/index.js +148 -21
  35. package/dist/slices/show-actor/index.d.ts +3 -2
  36. package/dist/slices/show-actor/index.js +46 -20
  37. package/dist/slices/show-aggregate-completeness/index.d.ts +3 -2
  38. package/dist/slices/show-aggregate-completeness/index.js +62 -20
  39. package/dist/slices/show-chapter/index.d.ts +2 -1
  40. package/dist/slices/show-chapter/index.js +14 -22
  41. package/dist/slices/show-command/index.d.ts +2 -1
  42. package/dist/slices/show-command/index.js +54 -19
  43. package/dist/slices/show-completeness/index.d.ts +3 -1
  44. package/dist/slices/show-completeness/index.js +313 -31
  45. package/dist/slices/show-event/index.d.ts +2 -1
  46. package/dist/slices/show-event/index.js +44 -20
  47. package/dist/slices/show-model-summary/index.d.ts +2 -1
  48. package/dist/slices/show-model-summary/index.js +18 -9
  49. package/dist/slices/show-slice/index.d.ts +2 -1
  50. package/dist/slices/show-slice/index.js +174 -24
  51. package/dist/slices/update-field/index.js +4 -33
  52. 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 <read-model> Show field mapping completeness status
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 <read-model-name>');
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,2 @@
1
+ import type { OutputFormat } from './format.js';
2
+ export declare function getDefaultFormat(): OutputFormat;
@@ -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,3 @@
1
+ export type OutputFormat = 'xml' | 'json';
2
+ export declare function escapeXml(str: string): string;
3
+ export declare function outputJson(data: unknown): void;
@@ -0,0 +1,11 @@
1
+ export function escapeXml(str) {
2
+ return str
3
+ .replace(/&/g, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&apos;');
8
+ }
9
+ export function outputJson(data) {
10
+ console.log(JSON.stringify(data, null, 2));
11
+ }