eventmodeler 0.4.1 → 0.4.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/dist/cloud/slices/index.d.ts +64 -10
- package/dist/cloud/slices/index.js +183 -24
- package/dist/index.js +122 -95
- package/dist/lib/auth.js +0 -1
- package/dist/lib/backend.d.ts +0 -5
- package/dist/lib/backend.js +3 -33
- package/dist/lib/cloud-client.d.ts +1 -2
- package/dist/lib/cloud-client.js +69 -233
- package/dist/lib/slice-utils.d.ts +21 -0
- package/dist/lib/slice-utils.js +61 -0
- package/dist/slices/add-scenario/index.d.ts +27 -0
- package/dist/slices/add-scenario/index.js +283 -133
- package/dist/slices/codegen-chapter-events/index.d.ts +2 -0
- package/dist/slices/codegen-chapter-events/index.js +145 -0
- package/dist/slices/login/index.js +1 -5
- package/package.json +1 -1
|
@@ -84,21 +84,40 @@ export declare function mapFields(modelId: string, sourceName: string, targetNam
|
|
|
84
84
|
export interface ScenarioInput {
|
|
85
85
|
name: string;
|
|
86
86
|
description?: string;
|
|
87
|
-
given?: {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
87
|
+
given?: Array<{
|
|
88
|
+
event: string;
|
|
89
|
+
fieldValues?: Record<string, unknown>;
|
|
90
|
+
}>;
|
|
91
91
|
when?: {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
command?: {
|
|
93
|
+
command: string;
|
|
94
|
+
fieldValues?: Record<string, unknown>;
|
|
95
|
+
};
|
|
96
|
+
events?: Array<{
|
|
97
|
+
event: string;
|
|
98
|
+
fieldValues?: Record<string, unknown>;
|
|
99
|
+
}>;
|
|
95
100
|
};
|
|
96
101
|
then: {
|
|
97
|
-
type: 'events' | 'error' | 'command';
|
|
98
|
-
events?:
|
|
102
|
+
type: 'events' | 'error' | 'command' | 'readModelAssertion' | 'noCommand';
|
|
103
|
+
events?: Array<{
|
|
104
|
+
event: string;
|
|
105
|
+
fieldValues?: Record<string, unknown>;
|
|
106
|
+
}>;
|
|
99
107
|
errorMessage?: string;
|
|
100
108
|
errorType?: string;
|
|
101
|
-
command?:
|
|
109
|
+
command?: {
|
|
110
|
+
command: string;
|
|
111
|
+
fieldValues?: Record<string, unknown>;
|
|
112
|
+
};
|
|
113
|
+
readModelAssertion?: {
|
|
114
|
+
readModel: string;
|
|
115
|
+
givenEvents?: Array<{
|
|
116
|
+
event: string;
|
|
117
|
+
fieldValues?: Record<string, unknown>;
|
|
118
|
+
}>;
|
|
119
|
+
expectedFieldValues?: Record<string, unknown>;
|
|
120
|
+
};
|
|
102
121
|
};
|
|
103
122
|
}
|
|
104
123
|
export declare function createScenario(modelId: string, sliceName: string, scenario: ScenarioInput): Promise<{
|
|
@@ -118,6 +137,7 @@ export interface CompoundFieldInput {
|
|
|
118
137
|
isOptional?: boolean;
|
|
119
138
|
isGenerated?: boolean;
|
|
120
139
|
isUserInput?: boolean;
|
|
140
|
+
subfields?: CompoundFieldInput[];
|
|
121
141
|
}
|
|
122
142
|
export interface StateChangeSliceInput {
|
|
123
143
|
sliceName: string;
|
|
@@ -200,6 +220,40 @@ export declare function showChapter(modelId: string, chapterName: string, format
|
|
|
200
220
|
export declare function showActor(modelId: string, actorName: string, format?: OutputFormat): Promise<string>;
|
|
201
221
|
export declare function exportJson(modelId: string): Promise<string>;
|
|
202
222
|
export declare function codegenSlice(modelId: string, sliceName: string): Promise<string>;
|
|
223
|
+
export interface CodegenChapterEventsField {
|
|
224
|
+
name: string;
|
|
225
|
+
type: string;
|
|
226
|
+
list?: boolean;
|
|
227
|
+
generated?: boolean;
|
|
228
|
+
optional?: boolean;
|
|
229
|
+
userInput?: boolean;
|
|
230
|
+
subfields?: CodegenChapterEventsField[];
|
|
231
|
+
}
|
|
232
|
+
export interface CodegenChapterEventsChapter {
|
|
233
|
+
id: string;
|
|
234
|
+
name: string;
|
|
235
|
+
parent?: CodegenChapterEventsChapter | null;
|
|
236
|
+
}
|
|
237
|
+
export interface CodegenChapterEventsSlice {
|
|
238
|
+
id: string;
|
|
239
|
+
name: string;
|
|
240
|
+
sliceType: string;
|
|
241
|
+
}
|
|
242
|
+
export interface CodegenChapterEventsEvent {
|
|
243
|
+
id: string;
|
|
244
|
+
name: string;
|
|
245
|
+
fields: CodegenChapterEventsField[];
|
|
246
|
+
aggregate?: string;
|
|
247
|
+
sourceSlices: string[];
|
|
248
|
+
}
|
|
249
|
+
export interface CodegenChapterEventsResponse {
|
|
250
|
+
chapter: CodegenChapterEventsChapter | null;
|
|
251
|
+
sliceCount: number;
|
|
252
|
+
eventCount: number;
|
|
253
|
+
slices: CodegenChapterEventsSlice[];
|
|
254
|
+
events: CodegenChapterEventsEvent[];
|
|
255
|
+
}
|
|
256
|
+
export declare function codegenEvents(modelId: string, chapterName?: string): Promise<CodegenChapterEventsResponse>;
|
|
203
257
|
export declare function listAggregates(modelId: string, format?: OutputFormat): Promise<string>;
|
|
204
258
|
export declare function listReadModels(modelId: string, format?: OutputFormat): Promise<string>;
|
|
205
259
|
export declare function listScreens(modelId: string, format?: OutputFormat): Promise<string>;
|
|
@@ -11,16 +11,7 @@ import { getValidAccessToken } from '../../lib/auth.js';
|
|
|
11
11
|
*/
|
|
12
12
|
async function handleHttpError(response, path) {
|
|
13
13
|
const text = await response.text().catch(() => '');
|
|
14
|
-
|
|
15
|
-
if (text.trim()) {
|
|
16
|
-
try {
|
|
17
|
-
const parsed = JSON.parse(text);
|
|
18
|
-
errorDetail = parsed.error ?? parsed.message ?? '';
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
// Response body isn't JSON (could be HTML error page, etc.)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
14
|
+
const errorDetail = extractErrorDetail(text);
|
|
24
15
|
switch (response.status) {
|
|
25
16
|
case 401:
|
|
26
17
|
throw new Error(`Authentication required. Run 'eventmodeler login' to authenticate.` +
|
|
@@ -34,16 +25,83 @@ async function handleHttpError(response, path) {
|
|
|
34
25
|
throw new Error(errorDetail || `HTTP ${response.status}: ${response.statusText}`);
|
|
35
26
|
}
|
|
36
27
|
}
|
|
28
|
+
const GENERIC_ERROR_MESSAGES = new Set([
|
|
29
|
+
'internal server error',
|
|
30
|
+
'bad request',
|
|
31
|
+
'unauthorized',
|
|
32
|
+
'forbidden',
|
|
33
|
+
'not found',
|
|
34
|
+
]);
|
|
35
|
+
function isGenericErrorMessage(value) {
|
|
36
|
+
return GENERIC_ERROR_MESSAGES.has(value.trim().toLowerCase());
|
|
37
|
+
}
|
|
38
|
+
function toStringValue(value) {
|
|
39
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
40
|
+
}
|
|
41
|
+
function collectCandidateMessages(node, out) {
|
|
42
|
+
if (!node || typeof node !== 'object')
|
|
43
|
+
return;
|
|
44
|
+
const obj = node;
|
|
45
|
+
// Prefer richer fields before generic "error".
|
|
46
|
+
for (const key of ['message', 'detail', 'reason', 'description', 'title', 'error']) {
|
|
47
|
+
const value = toStringValue(obj[key]);
|
|
48
|
+
if (value)
|
|
49
|
+
out.push(value);
|
|
50
|
+
}
|
|
51
|
+
const errors = obj.errors;
|
|
52
|
+
if (Array.isArray(errors)) {
|
|
53
|
+
for (const entry of errors) {
|
|
54
|
+
if (typeof entry === 'string') {
|
|
55
|
+
const value = entry.trim();
|
|
56
|
+
if (value)
|
|
57
|
+
out.push(value);
|
|
58
|
+
}
|
|
59
|
+
else if (entry && typeof entry === 'object') {
|
|
60
|
+
collectCandidateMessages(entry, out);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const cause = obj.cause;
|
|
65
|
+
if (cause && typeof cause === 'object') {
|
|
66
|
+
collectCandidateMessages(cause, out);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function extractErrorDetail(text) {
|
|
70
|
+
const body = text.trim();
|
|
71
|
+
if (!body)
|
|
72
|
+
return '';
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(body);
|
|
75
|
+
if (typeof parsed === 'string') {
|
|
76
|
+
return parsed.trim();
|
|
77
|
+
}
|
|
78
|
+
const candidates = [];
|
|
79
|
+
collectCandidateMessages(parsed, candidates);
|
|
80
|
+
if (candidates.length > 0) {
|
|
81
|
+
const specific = candidates.find((value) => !isGenericErrorMessage(value));
|
|
82
|
+
return specific ?? candidates[0];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Not JSON. Fall back to plain text below.
|
|
87
|
+
}
|
|
88
|
+
// Avoid dumping entire HTML pages from reverse proxies.
|
|
89
|
+
if (body.startsWith('<')) {
|
|
90
|
+
const title = body.match(/<title>([^<]+)<\/title>/i)?.[1]?.trim();
|
|
91
|
+
return title && !isGenericErrorMessage(title) ? title : '';
|
|
92
|
+
}
|
|
93
|
+
return body;
|
|
94
|
+
}
|
|
37
95
|
async function cliGet(path, params) {
|
|
38
96
|
const backendUrl = getBackendUrl();
|
|
39
97
|
const accessToken = await getValidAccessToken();
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
const response = await fetch(
|
|
98
|
+
// Build query string manually using encodeURIComponent (uses %20 for spaces, not +)
|
|
99
|
+
const queryParts = Object.entries(params)
|
|
100
|
+
.filter(([_, value]) => value !== undefined)
|
|
101
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
102
|
+
const queryString = queryParts.length > 0 ? `?${queryParts.join('&')}` : '';
|
|
103
|
+
const fullUrl = `${backendUrl}${path}${queryString}`;
|
|
104
|
+
const response = await fetch(fullUrl, {
|
|
47
105
|
method: 'GET',
|
|
48
106
|
headers: {
|
|
49
107
|
Accept: 'application/json',
|
|
@@ -166,13 +224,14 @@ function formatToMediaType(format) {
|
|
|
166
224
|
async function cliGetText(path, params, format) {
|
|
167
225
|
const backendUrl = getBackendUrl();
|
|
168
226
|
const accessToken = await getValidAccessToken();
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
const
|
|
227
|
+
// Build query string manually using encodeURIComponent (uses %20 for spaces, not +)
|
|
228
|
+
// Some servers don't decode + as space in query parameters
|
|
229
|
+
const queryParts = Object.entries(params)
|
|
230
|
+
.filter(([_, value]) => value !== undefined)
|
|
231
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
232
|
+
const queryString = queryParts.length > 0 ? `?${queryParts.join('&')}` : '';
|
|
233
|
+
const fullUrl = `${backendUrl}${path}${queryString}`;
|
|
234
|
+
const response = await fetch(fullUrl, {
|
|
176
235
|
method: 'GET',
|
|
177
236
|
headers: {
|
|
178
237
|
Accept: formatToMediaType(format),
|
|
@@ -206,6 +265,106 @@ export async function exportJson(modelId) {
|
|
|
206
265
|
export async function codegenSlice(modelId, sliceName) {
|
|
207
266
|
return cliGetText('/cli/codegen-slice', { modelId, name: sliceName });
|
|
208
267
|
}
|
|
268
|
+
function countFieldsDeep(fields) {
|
|
269
|
+
if (!fields || fields.length === 0) {
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
return fields.reduce((count, field) => count + 1 + countFieldsDeep(field.subfields), 0);
|
|
273
|
+
}
|
|
274
|
+
function normalizeName(name) {
|
|
275
|
+
return (name ?? '').trim().toLowerCase();
|
|
276
|
+
}
|
|
277
|
+
export async function codegenEvents(modelId, chapterName) {
|
|
278
|
+
const slicesOutput = await listSlices(modelId, 'json', chapterName);
|
|
279
|
+
let slicesPayload;
|
|
280
|
+
try {
|
|
281
|
+
slicesPayload = JSON.parse(slicesOutput);
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
285
|
+
throw new Error(`Invalid JSON from /cli/list-slices: ${message}`);
|
|
286
|
+
}
|
|
287
|
+
const slices = (slicesPayload.slices ?? [])
|
|
288
|
+
.filter((slice) => typeof slice.id === 'string' && typeof slice.name === 'string');
|
|
289
|
+
if (slices.length === 0) {
|
|
290
|
+
return {
|
|
291
|
+
chapter: null,
|
|
292
|
+
sliceCount: 0,
|
|
293
|
+
eventCount: 0,
|
|
294
|
+
slices: [],
|
|
295
|
+
events: [],
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const codegenOutputs = await Promise.all(slices.map(async (slice) => {
|
|
299
|
+
const raw = await codegenSlice(modelId, slice.name);
|
|
300
|
+
try {
|
|
301
|
+
return JSON.parse(raw);
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
305
|
+
throw new Error(`Invalid JSON from /cli/codegen-slice for "${slice.name}": ${message}`);
|
|
306
|
+
}
|
|
307
|
+
}));
|
|
308
|
+
const aggregatedSlices = [];
|
|
309
|
+
const eventsByName = new Map();
|
|
310
|
+
let chapter = null;
|
|
311
|
+
for (let i = 0; i < codegenOutputs.length; i++) {
|
|
312
|
+
const codegenOutput = codegenOutputs[i];
|
|
313
|
+
const fallbackSlice = slices[i];
|
|
314
|
+
const sliceName = codegenOutput.slice?.name ?? fallbackSlice.name;
|
|
315
|
+
aggregatedSlices.push({
|
|
316
|
+
id: codegenOutput.slice?.id ?? fallbackSlice.id,
|
|
317
|
+
name: sliceName,
|
|
318
|
+
sliceType: codegenOutput.sliceType ?? 'STATE_CHANGE',
|
|
319
|
+
});
|
|
320
|
+
if (chapterName && !chapter && codegenOutput.chapter) {
|
|
321
|
+
chapter = codegenOutput.chapter;
|
|
322
|
+
}
|
|
323
|
+
for (const event of codegenOutput.elements?.events ?? []) {
|
|
324
|
+
if (!event.name) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const key = normalizeName(event.name);
|
|
328
|
+
if (!key) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const existing = eventsByName.get(key);
|
|
332
|
+
if (!existing) {
|
|
333
|
+
eventsByName.set(key, {
|
|
334
|
+
id: event.id ?? '',
|
|
335
|
+
name: event.name,
|
|
336
|
+
fields: event.fields ?? [],
|
|
337
|
+
aggregate: event.aggregate,
|
|
338
|
+
sourceSlices: [sliceName],
|
|
339
|
+
});
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (!existing.sourceSlices.includes(sliceName)) {
|
|
343
|
+
existing.sourceSlices.push(sliceName);
|
|
344
|
+
}
|
|
345
|
+
if (!existing.aggregate && event.aggregate) {
|
|
346
|
+
existing.aggregate = event.aggregate;
|
|
347
|
+
}
|
|
348
|
+
if (countFieldsDeep(event.fields) > countFieldsDeep(existing.fields)) {
|
|
349
|
+
existing.fields = event.fields ?? [];
|
|
350
|
+
}
|
|
351
|
+
if (!existing.id && event.id) {
|
|
352
|
+
existing.id = event.id;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const events = [...eventsByName.values()]
|
|
357
|
+
.map(event => ({ ...event, sourceSlices: [...event.sourceSlices].sort((a, b) => a.localeCompare(b)) }))
|
|
358
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
359
|
+
const sortedSlices = [...aggregatedSlices].sort((a, b) => a.name.localeCompare(b.name));
|
|
360
|
+
return {
|
|
361
|
+
chapter,
|
|
362
|
+
sliceCount: sortedSlices.length,
|
|
363
|
+
eventCount: events.length,
|
|
364
|
+
slices: sortedSlices,
|
|
365
|
+
events,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
209
368
|
export async function listAggregates(modelId, format = 'xml') {
|
|
210
369
|
return cliGetText('/cli/list-aggregates', { modelId }, format);
|
|
211
370
|
}
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import { openApp } from './slices/open-app/index.js';
|
|
|
22
22
|
import { search } from './slices/search/index.js';
|
|
23
23
|
import { listChapters } from './slices/list-chapters/index.js';
|
|
24
24
|
import { showChapter } from './slices/show-chapter/index.js';
|
|
25
|
-
import { addScenario } from './slices/add-scenario/index.js';
|
|
25
|
+
import { addScenario, parseScenarioInput } from './slices/add-scenario/index.js';
|
|
26
26
|
import { addField } from './slices/add-field/index.js';
|
|
27
27
|
import { removeScenario } from './slices/remove-scenario/index.js';
|
|
28
28
|
import { removeField } from './slices/remove-field/index.js';
|
|
@@ -45,6 +45,7 @@ import { createAutomationSlice } from './slices/create-automation-slice/index.js
|
|
|
45
45
|
import { createStateViewSlice } from './slices/create-state-view-slice/index.js';
|
|
46
46
|
import { createFlow } from './slices/create-flow/index.js';
|
|
47
47
|
import { codegenSlice } from './slices/codegen-slice/index.js';
|
|
48
|
+
import { codegenEvents } from './slices/codegen-chapter-events/index.js';
|
|
48
49
|
import { diff } from './slices/diff/index.js';
|
|
49
50
|
import { merge } from './slices/merge/index.js';
|
|
50
51
|
import { gitSetup, gitStatus } from './slices/git/index.js';
|
|
@@ -69,6 +70,17 @@ function getNamedArg(argList, ...names) {
|
|
|
69
70
|
function hasHelpFlag(argList) {
|
|
70
71
|
return argList.includes('-h') || argList.includes('--help');
|
|
71
72
|
}
|
|
73
|
+
function toCompoundFields(fields) {
|
|
74
|
+
return fields.map(field => ({
|
|
75
|
+
name: field.name,
|
|
76
|
+
fieldType: field.type,
|
|
77
|
+
isList: field.isList,
|
|
78
|
+
isOptional: field.isOptional,
|
|
79
|
+
isGenerated: field.isGenerated,
|
|
80
|
+
isUserInput: field.isUserInput,
|
|
81
|
+
subfields: field.subfields ? toCompoundFields(field.subfields) : undefined,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
72
84
|
function printHelp() {
|
|
73
85
|
console.log(`
|
|
74
86
|
eventmodeler - CLI tool for interacting with Event Model files
|
|
@@ -145,6 +157,8 @@ COMMANDS:
|
|
|
145
157
|
Create a flow between elements (Event→ReadModel, ReadModel→Screen/Processor)
|
|
146
158
|
|
|
147
159
|
codegen slice <name> Generate code-ready JSON for a slice (includes dependencies, mappings, scenarios)
|
|
160
|
+
codegen events [--chapter <name>]
|
|
161
|
+
Generate deduplicated events for a chapter or the whole model
|
|
148
162
|
|
|
149
163
|
summary Show model summary statistics
|
|
150
164
|
|
|
@@ -1743,91 +1757,49 @@ EXAMPLES:
|
|
|
1743
1757
|
process.exit(1);
|
|
1744
1758
|
}
|
|
1745
1759
|
if (isCloudMode) {
|
|
1746
|
-
|
|
1747
|
-
let scenario;
|
|
1760
|
+
let parsedScenario;
|
|
1748
1761
|
try {
|
|
1749
|
-
|
|
1750
|
-
const parsed = JSON.parse(inputData);
|
|
1751
|
-
scenario = {
|
|
1752
|
-
name: parsed.name,
|
|
1753
|
-
description: parsed.description,
|
|
1754
|
-
given: parsed.given,
|
|
1755
|
-
when: parsed.when,
|
|
1756
|
-
then: parsed.then,
|
|
1757
|
-
};
|
|
1758
|
-
}
|
|
1759
|
-
else {
|
|
1760
|
-
// XML parsing - extract scenario attributes
|
|
1761
|
-
const nameMatch = inputData.match(/name="([^"]*)"/);
|
|
1762
|
-
const descMatch = inputData.match(/<description>([^<]*)<\/description>/);
|
|
1763
|
-
// Parse given events
|
|
1764
|
-
const givenMatch = inputData.match(/<given[^>]*type="events"[^>]*>([\s\S]*?)<\/given>/);
|
|
1765
|
-
let given;
|
|
1766
|
-
if (givenMatch) {
|
|
1767
|
-
const eventMatches = givenMatch[1].match(/<event>([^<]*)<\/event>/g);
|
|
1768
|
-
if (eventMatches) {
|
|
1769
|
-
given = {
|
|
1770
|
-
type: 'events',
|
|
1771
|
-
events: eventMatches.map(m => m.replace(/<\/?event>/g, '')),
|
|
1772
|
-
};
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
// Parse when
|
|
1776
|
-
const whenCommandMatch = inputData.match(/<when[^>]*type="command"[^>]*>[\s\S]*?<command>([^<]*)<\/command>[\s\S]*?<\/when>/);
|
|
1777
|
-
const whenEventsMatch = inputData.match(/<when[^>]*type="events"[^>]*>([\s\S]*?)<\/when>/);
|
|
1778
|
-
let when;
|
|
1779
|
-
if (whenCommandMatch) {
|
|
1780
|
-
when = { type: 'command', command: whenCommandMatch[1] };
|
|
1781
|
-
}
|
|
1782
|
-
else if (whenEventsMatch) {
|
|
1783
|
-
const eventMatches = whenEventsMatch[1].match(/<event>([^<]*)<\/event>/g);
|
|
1784
|
-
if (eventMatches) {
|
|
1785
|
-
when = {
|
|
1786
|
-
type: 'events',
|
|
1787
|
-
events: eventMatches.map(m => m.replace(/<\/?event>/g, '')),
|
|
1788
|
-
};
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
// Parse then
|
|
1792
|
-
const thenEventsMatch = inputData.match(/<then[^>]*type="events"[^>]*>([\s\S]*?)<\/then>/);
|
|
1793
|
-
const thenErrorMatch = inputData.match(/<then[^>]*type="error"[^>]*>([\s\S]*?)<\/then>/);
|
|
1794
|
-
const thenCommandMatch = inputData.match(/<then[^>]*type="command"[^>]*>[\s\S]*?<command>([^<]*)<\/command>[\s\S]*?<\/then>/);
|
|
1795
|
-
let then;
|
|
1796
|
-
if (thenEventsMatch) {
|
|
1797
|
-
const eventMatches = thenEventsMatch[1].match(/<event>([^<]*)<\/event>/g);
|
|
1798
|
-
then = {
|
|
1799
|
-
type: 'events',
|
|
1800
|
-
events: eventMatches ? eventMatches.map(m => m.replace(/<\/?event>/g, '')) : [],
|
|
1801
|
-
};
|
|
1802
|
-
}
|
|
1803
|
-
else if (thenErrorMatch) {
|
|
1804
|
-
const msgMatch = thenErrorMatch[1].match(/<message>([^<]*)<\/message>/);
|
|
1805
|
-
const typeMatch = thenErrorMatch[1].match(/<errorType>([^<]*)<\/errorType>/);
|
|
1806
|
-
then = {
|
|
1807
|
-
type: 'error',
|
|
1808
|
-
errorMessage: msgMatch?.[1],
|
|
1809
|
-
errorType: typeMatch?.[1],
|
|
1810
|
-
};
|
|
1811
|
-
}
|
|
1812
|
-
else if (thenCommandMatch) {
|
|
1813
|
-
then = { type: 'command', command: thenCommandMatch[1] };
|
|
1814
|
-
}
|
|
1815
|
-
else {
|
|
1816
|
-
throw new Error('Could not parse <then> element');
|
|
1817
|
-
}
|
|
1818
|
-
scenario = {
|
|
1819
|
-
name: nameMatch?.[1] ?? '',
|
|
1820
|
-
description: descMatch?.[1],
|
|
1821
|
-
given,
|
|
1822
|
-
when,
|
|
1823
|
-
then,
|
|
1824
|
-
};
|
|
1825
|
-
}
|
|
1762
|
+
parsedScenario = parseScenarioInput(inputData);
|
|
1826
1763
|
}
|
|
1827
1764
|
catch (err) {
|
|
1828
1765
|
console.error(`Error parsing scenario data: ${err.message}`);
|
|
1829
1766
|
process.exit(1);
|
|
1830
1767
|
}
|
|
1768
|
+
const scenario = {
|
|
1769
|
+
name: parsedScenario.name,
|
|
1770
|
+
description: parsedScenario.description,
|
|
1771
|
+
given: parsedScenario.given,
|
|
1772
|
+
when: parsedScenario.when
|
|
1773
|
+
? {
|
|
1774
|
+
command: parsedScenario.when.command
|
|
1775
|
+
? {
|
|
1776
|
+
command: parsedScenario.when.command,
|
|
1777
|
+
fieldValues: parsedScenario.when.commandFieldValues,
|
|
1778
|
+
}
|
|
1779
|
+
: undefined,
|
|
1780
|
+
events: parsedScenario.when.events,
|
|
1781
|
+
}
|
|
1782
|
+
: undefined,
|
|
1783
|
+
then: {
|
|
1784
|
+
type: parsedScenario.then.type,
|
|
1785
|
+
events: parsedScenario.then.events,
|
|
1786
|
+
errorMessage: parsedScenario.then.errorMessage,
|
|
1787
|
+
errorType: parsedScenario.then.errorType,
|
|
1788
|
+
command: parsedScenario.then.command
|
|
1789
|
+
? {
|
|
1790
|
+
command: parsedScenario.then.command,
|
|
1791
|
+
fieldValues: parsedScenario.then.commandFieldValues,
|
|
1792
|
+
}
|
|
1793
|
+
: undefined,
|
|
1794
|
+
readModelAssertion: parsedScenario.then.readModel
|
|
1795
|
+
? {
|
|
1796
|
+
readModel: parsedScenario.then.readModel,
|
|
1797
|
+
givenEvents: parsedScenario.then.givenEvents,
|
|
1798
|
+
expectedFieldValues: parsedScenario.then.expected,
|
|
1799
|
+
}
|
|
1800
|
+
: undefined,
|
|
1801
|
+
},
|
|
1802
|
+
};
|
|
1831
1803
|
await cloudSlices.createScenario(modelId, sliceArg, scenario);
|
|
1832
1804
|
console.log(`Created scenario "${scenario.name}" in slice "${sliceArg}"`);
|
|
1833
1805
|
}
|
|
@@ -2537,15 +2509,13 @@ EXAMPLES:
|
|
|
2537
2509
|
// Parse XML input to extract structured data
|
|
2538
2510
|
const parser = await import('./lib/slice-utils.js');
|
|
2539
2511
|
const parsed = parser.parseStateChangeSliceXml(xmlArg);
|
|
2540
|
-
// Convert FieldInput to CompoundFieldInput (rename 'type' to 'fieldType')
|
|
2541
|
-
const convertFields = (fields) => fields.map(f => ({ ...f, fieldType: f.type, type: undefined }));
|
|
2542
2512
|
const cloudInput = {
|
|
2543
2513
|
sliceName: parsed.sliceName,
|
|
2544
2514
|
after: parsed.after,
|
|
2545
2515
|
before: parsed.before,
|
|
2546
|
-
screen: { name: parsed.screen.name, fields:
|
|
2547
|
-
command: { name: parsed.command.name, fields:
|
|
2548
|
-
event: { name: parsed.event.name, fields:
|
|
2516
|
+
screen: { name: parsed.screen.name, fields: toCompoundFields(parsed.screen.fields) },
|
|
2517
|
+
command: { name: parsed.command.name, fields: toCompoundFields(parsed.command.fields) },
|
|
2518
|
+
event: { name: parsed.event.name, fields: toCompoundFields(parsed.event.fields) },
|
|
2549
2519
|
};
|
|
2550
2520
|
const result = await cloudSlices.createStateChangeSlice(modelId, cloudInput);
|
|
2551
2521
|
console.log(`Created state-change slice "${parsed.sliceName}"`);
|
|
@@ -2639,10 +2609,30 @@ EXAMPLES:
|
|
|
2639
2609
|
process.exit(1);
|
|
2640
2610
|
}
|
|
2641
2611
|
if (isCloudMode) {
|
|
2642
|
-
|
|
2643
|
-
|
|
2612
|
+
// Parse XML input to extract structured data
|
|
2613
|
+
const parser = await import('./lib/slice-utils.js');
|
|
2614
|
+
const parsed = parser.parseAutomationSliceXml(xmlArg);
|
|
2615
|
+
const cloudInput = {
|
|
2616
|
+
sliceName: parsed.sliceName,
|
|
2617
|
+
after: parsed.after,
|
|
2618
|
+
before: parsed.before,
|
|
2619
|
+
readModel: { name: parsed.readModel.name, fields: toCompoundFields(parsed.readModel.fields) },
|
|
2620
|
+
processor: { name: parsed.processor.name },
|
|
2621
|
+
command: { name: parsed.command.name, fields: toCompoundFields(parsed.command.fields) },
|
|
2622
|
+
event: { name: parsed.event.name, fields: toCompoundFields(parsed.event.fields) },
|
|
2623
|
+
};
|
|
2624
|
+
const result = await cloudSlices.createAutomationSlice(modelId, cloudInput);
|
|
2625
|
+
console.log(`Created automation slice "${parsed.sliceName}"`);
|
|
2626
|
+
console.log(` ReadModel: ${parsed.readModel.name} (${parsed.readModel.fields.length} fields)`);
|
|
2627
|
+
console.log(` Processor: ${parsed.processor.name}`);
|
|
2628
|
+
console.log(` Command: ${parsed.command.name} (${parsed.command.fields.length} fields)`);
|
|
2629
|
+
console.log(` Event: ${parsed.event.name} (${parsed.event.fields.length} fields)`);
|
|
2630
|
+
console.log(` ReadModel -> Command mappings: ${result.readModelToCommandMappings}`);
|
|
2631
|
+
console.log(` Command -> Event mappings: ${result.commandToEventMappings}`);
|
|
2632
|
+
}
|
|
2633
|
+
else {
|
|
2634
|
+
createAutomationSlice(model, filePath, xmlArg);
|
|
2644
2635
|
}
|
|
2645
|
-
createAutomationSlice(model, filePath, xmlArg);
|
|
2646
2636
|
break;
|
|
2647
2637
|
}
|
|
2648
2638
|
case 'state-view-slice': {
|
|
@@ -2713,13 +2703,11 @@ EXAMPLES:
|
|
|
2713
2703
|
// Parse XML input to extract structured data
|
|
2714
2704
|
const parser = await import('./lib/slice-utils.js');
|
|
2715
2705
|
const parsed = parser.parseStateViewSliceXml(xmlArg);
|
|
2716
|
-
// Convert FieldInput to CompoundFieldInput (rename 'type' to 'fieldType')
|
|
2717
|
-
const convertFields = (fields) => fields.map(f => ({ ...f, fieldType: f.type, type: undefined }));
|
|
2718
2706
|
const cloudInput = {
|
|
2719
2707
|
sliceName: parsed.sliceName,
|
|
2720
2708
|
after: parsed.after,
|
|
2721
2709
|
before: parsed.before,
|
|
2722
|
-
readModel: { name: parsed.readModel.name, fields:
|
|
2710
|
+
readModel: { name: parsed.readModel.name, fields: toCompoundFields(parsed.readModel.fields) },
|
|
2723
2711
|
};
|
|
2724
2712
|
const result = await cloudSlices.createStateViewSlice(modelId, cloudInput);
|
|
2725
2713
|
console.log(`Created state-view slice "${parsed.sliceName}"`);
|
|
@@ -2787,9 +2775,10 @@ USAGE:
|
|
|
2787
2775
|
eventmodeler codegen <type> [options]
|
|
2788
2776
|
|
|
2789
2777
|
TYPES:
|
|
2790
|
-
slice
|
|
2778
|
+
slice Generate comprehensive JSON for a slice
|
|
2779
|
+
events Generate deduplicated events for a chapter or entire model
|
|
2791
2780
|
|
|
2792
|
-
Run "eventmodeler codegen
|
|
2781
|
+
Run "eventmodeler codegen <type> --help" for detailed help.
|
|
2793
2782
|
`);
|
|
2794
2783
|
process.exit(0);
|
|
2795
2784
|
}
|
|
@@ -2824,7 +2813,7 @@ EXAMPLES:
|
|
|
2824
2813
|
`);
|
|
2825
2814
|
process.exit(0);
|
|
2826
2815
|
}
|
|
2827
|
-
const sliceName =
|
|
2816
|
+
const sliceName = target;
|
|
2828
2817
|
if (!sliceName) {
|
|
2829
2818
|
console.error('Error: slice name is required');
|
|
2830
2819
|
console.error('Usage: eventmodeler codegen slice <name>');
|
|
@@ -2840,9 +2829,47 @@ EXAMPLES:
|
|
|
2840
2829
|
}
|
|
2841
2830
|
break;
|
|
2842
2831
|
}
|
|
2832
|
+
case 'events': {
|
|
2833
|
+
if (helpRequested) {
|
|
2834
|
+
console.log(`
|
|
2835
|
+
eventmodeler codegen events - Generate deduplicated events for scaffolding
|
|
2836
|
+
|
|
2837
|
+
USAGE:
|
|
2838
|
+
eventmodeler codegen events [--chapter <chapter-name>]
|
|
2839
|
+
eventmodeler codegen events <chapter-name>
|
|
2840
|
+
|
|
2841
|
+
ARGUMENTS:
|
|
2842
|
+
<chapter-name> Optional chapter name (if omitted, all model events are returned)
|
|
2843
|
+
|
|
2844
|
+
OUTPUT:
|
|
2845
|
+
JSON payload including:
|
|
2846
|
+
- chapter hierarchy metadata (when chapter-scoped)
|
|
2847
|
+
- all relevant slices
|
|
2848
|
+
- deduplicated events across those slices
|
|
2849
|
+
- sourceSlices per event (where each event appears)
|
|
2850
|
+
|
|
2851
|
+
This is designed for event scaffolding flows (e.g. events.kt generation).
|
|
2852
|
+
|
|
2853
|
+
EXAMPLES:
|
|
2854
|
+
eventmodeler codegen events --chapter "Register Products"
|
|
2855
|
+
eventmodeler codegen events "Register Products"
|
|
2856
|
+
eventmodeler codegen events > model-events.json
|
|
2857
|
+
`);
|
|
2858
|
+
process.exit(0);
|
|
2859
|
+
}
|
|
2860
|
+
const chapterName = chapterArg ?? target;
|
|
2861
|
+
if (isCloudMode) {
|
|
2862
|
+
const output = await cloudSlices.codegenEvents(modelId, chapterName);
|
|
2863
|
+
console.log(JSON.stringify(output, null, 2));
|
|
2864
|
+
}
|
|
2865
|
+
else {
|
|
2866
|
+
codegenEvents(model, chapterName);
|
|
2867
|
+
}
|
|
2868
|
+
break;
|
|
2869
|
+
}
|
|
2843
2870
|
default:
|
|
2844
2871
|
console.error(`Unknown codegen target: ${subcommand}`);
|
|
2845
|
-
console.error('Valid targets: slice');
|
|
2872
|
+
console.error('Valid targets: slice, events');
|
|
2846
2873
|
console.error('Run "eventmodeler codegen --help" for more information.');
|
|
2847
2874
|
process.exit(1);
|
|
2848
2875
|
}
|
package/dist/lib/auth.js
CHANGED
|
@@ -130,7 +130,6 @@ export async function startAuthFlow() {
|
|
|
130
130
|
});
|
|
131
131
|
const authorizationUrl = `${keycloakUrl}/protocol/openid-connect/auth?${params}`;
|
|
132
132
|
console.log('\nOpening browser for authentication...');
|
|
133
|
-
console.log(`\nIf the browser doesn't open, visit:\n${authorizationUrl}\n`);
|
|
134
133
|
// Open the browser
|
|
135
134
|
openBrowser(authorizationUrl);
|
|
136
135
|
});
|
package/dist/lib/backend.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { EventModel } from '../types.js';
|
|
2
|
-
import { type CloudClient } from './cloud-client.js';
|
|
3
2
|
import { type ProjectConfig } from './project-config.js';
|
|
4
3
|
/**
|
|
5
4
|
* Backend abstraction for CLI operations.
|
|
@@ -21,10 +20,6 @@ export interface CliBackend {
|
|
|
21
20
|
* Create a local file-based backend.
|
|
22
21
|
*/
|
|
23
22
|
export declare function createLocalBackend(filePath: string): CliBackend;
|
|
24
|
-
/**
|
|
25
|
-
* Create a cloud backend using the Axon backend.
|
|
26
|
-
*/
|
|
27
|
-
export declare function createCloudBackendFromClient(client: CloudClient, modelId: string, modelName: string): CliBackend;
|
|
28
23
|
export interface ResolveBackendOptions {
|
|
29
24
|
/** Explicit file path (overrides auto-detection) */
|
|
30
25
|
filePath?: string;
|