eventmodeler 0.6.0 → 0.6.2
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/index.js +7133 -34
- package/package.json +5 -4
- package/dist/api/client-config.js +0 -10
- package/dist/api/generated/client/client.gen.js +0 -235
- package/dist/api/generated/client/index.js +0 -6
- package/dist/api/generated/client/types.gen.js +0 -2
- package/dist/api/generated/client/utils.gen.js +0 -228
- package/dist/api/generated/client.gen.js +0 -4
- package/dist/api/generated/core/auth.gen.js +0 -14
- package/dist/api/generated/core/bodySerializer.gen.js +0 -57
- package/dist/api/generated/core/params.gen.js +0 -100
- package/dist/api/generated/core/pathSerializer.gen.js +0 -106
- package/dist/api/generated/core/queryKeySerializer.gen.js +0 -92
- package/dist/api/generated/core/serverSentEvents.gen.js +0 -133
- package/dist/api/generated/core/types.gen.js +0 -2
- package/dist/api/generated/core/utils.gen.js +0 -87
- package/dist/api/generated/index.js +0 -2
- package/dist/api/generated/sdk.gen.js +0 -4222
- package/dist/api/generated/types.gen.js +0 -2
- package/dist/api/generated/zod.gen.js +0 -7217
- package/dist/commands/add.js +0 -315
- package/dist/commands/auth.js +0 -14
- package/dist/commands/create.js +0 -192
- package/dist/commands/design.js +0 -108
- package/dist/commands/guide.js +0 -15
- package/dist/commands/init.js +0 -21
- package/dist/commands/list-schemas.js +0 -177
- package/dist/commands/list.js +0 -39
- package/dist/commands/loop.js +0 -101
- package/dist/commands/map.js +0 -40
- package/dist/commands/mark.js +0 -27
- package/dist/commands/move.js +0 -35
- package/dist/commands/remove.js +0 -170
- package/dist/commands/rename.js +0 -53
- package/dist/commands/resize.js +0 -30
- package/dist/commands/search.js +0 -14
- package/dist/commands/set.js +0 -199
- package/dist/commands/show-schemas.js +0 -259
- package/dist/commands/show.js +0 -56
- package/dist/commands/summary.js +0 -13
- package/dist/commands/update.js +0 -240
- package/dist/lib/auth.js +0 -331
- package/dist/lib/config.js +0 -80
- package/dist/lib/excalidraw-schema.js +0 -66
- package/dist/lib/globals.js +0 -8
- package/dist/lib/model.js +0 -11
- package/dist/lib/project-config.js +0 -103
- package/dist/lib/resolve.js +0 -59
- package/dist/lib/scenario.js +0 -15
- package/dist/slices/add-scenario/index.js +0 -103
- package/dist/slices/guide/guides/codegen.js +0 -339
- package/dist/slices/guide/guides/connect-slices.js +0 -202
- package/dist/slices/guide/guides/create-slices.js +0 -273
- package/dist/slices/guide/guides/explore.js +0 -238
- package/dist/slices/guide/guides/information-flow.js +0 -304
- package/dist/slices/guide/guides/scenarios.js +0 -214
- package/dist/slices/guide/index.js +0 -40
- package/dist/slices/help/index.js +0 -96
- package/dist/slices/help/topics/build-codegen.js +0 -109
- package/dist/slices/help/topics/build-slice.js +0 -147
- package/dist/slices/help/topics/check-completeness.js +0 -57
- package/dist/slices/help/topics/connect-slices.js +0 -99
- package/dist/slices/help/topics/explore-model.js +0 -112
- package/dist/slices/help/topics/json-reference.js +0 -188
- package/dist/slices/help/topics/linked-copies.js +0 -89
- package/dist/slices/help/topics/manipulate-canvas.js +0 -150
- package/dist/slices/help/topics/write-scenarios.js +0 -162
- package/dist/slices/init/index.js +0 -86
- package/dist/slices/init/loop.js +0 -60
- package/dist/slices/login/index.js +0 -20
- package/dist/slices/logout/index.js +0 -14
- package/dist/slices/open-app/index.js +0 -36
- package/dist/slices/whoami/index.js +0 -19
package/dist/commands/update.js
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import { getGlobalId } from '../lib/globals';
|
|
2
|
-
import { requireModelId } from '../lib/model';
|
|
3
|
-
import { resolveAnyElement, unwrap, elementIdKey } from '../lib/resolve';
|
|
4
|
-
import * as sdk from '../api/generated/sdk.gen';
|
|
5
|
-
const RENAME_FIELD_MAP = {
|
|
6
|
-
command: sdk.renameCommandField,
|
|
7
|
-
event: sdk.renameEventField,
|
|
8
|
-
readmodel: sdk.renameReadModelField,
|
|
9
|
-
screen: sdk.renameScreenField,
|
|
10
|
-
processor: sdk.renameProcessorField,
|
|
11
|
-
'external-event': sdk.renameExternalEventField,
|
|
12
|
-
};
|
|
13
|
-
const CHANGE_TYPE_MAP = {
|
|
14
|
-
command: sdk.changeCommandFieldType,
|
|
15
|
-
event: sdk.changeEventFieldType,
|
|
16
|
-
readmodel: sdk.changeReadModelFieldType,
|
|
17
|
-
screen: sdk.changeScreenFieldType,
|
|
18
|
-
processor: sdk.changeProcessorFieldType,
|
|
19
|
-
'external-event': sdk.changeExternalEventFieldType,
|
|
20
|
-
};
|
|
21
|
-
const REORDER_FIELD_MAP = {
|
|
22
|
-
command: sdk.reorderCommandField,
|
|
23
|
-
event: sdk.reorderEventField,
|
|
24
|
-
readmodel: sdk.reorderReadModelField,
|
|
25
|
-
screen: sdk.reorderScreenField,
|
|
26
|
-
processor: sdk.reorderProcessorField,
|
|
27
|
-
'external-event': sdk.reorderExternalEventField,
|
|
28
|
-
};
|
|
29
|
-
const RENAME_SUBFIELD_MAP = {
|
|
30
|
-
command: sdk.renameCommandSubfield,
|
|
31
|
-
event: sdk.renameEventSubfield,
|
|
32
|
-
readmodel: sdk.renameReadModelSubfield,
|
|
33
|
-
screen: sdk.renameScreenSubfield,
|
|
34
|
-
processor: sdk.renameProcessorSubfield,
|
|
35
|
-
'external-event': sdk.renameExternalEventSubfield,
|
|
36
|
-
};
|
|
37
|
-
const CHANGE_SUBFIELD_TYPE_MAP = {
|
|
38
|
-
command: sdk.changeCommandSubfieldType,
|
|
39
|
-
event: sdk.changeEventSubfieldType,
|
|
40
|
-
readmodel: sdk.changeReadModelSubfieldType,
|
|
41
|
-
screen: sdk.changeScreenSubfieldType,
|
|
42
|
-
processor: sdk.changeProcessorSubfieldType,
|
|
43
|
-
'external-event': sdk.changeExternalEventSubfieldType,
|
|
44
|
-
};
|
|
45
|
-
const REORDER_SUBFIELD_MAP = {
|
|
46
|
-
command: sdk.reorderCommandSubfield,
|
|
47
|
-
event: sdk.reorderEventSubfield,
|
|
48
|
-
readmodel: sdk.reorderReadModelSubfield,
|
|
49
|
-
screen: sdk.reorderScreenSubfield,
|
|
50
|
-
processor: sdk.reorderProcessorSubfield,
|
|
51
|
-
'external-event': sdk.reorderExternalEventSubfield,
|
|
52
|
-
};
|
|
53
|
-
const buildMarkMap = () => ({
|
|
54
|
-
command: {
|
|
55
|
-
optional: { mark: sdk.markCommandFieldAsOptional, unmark: sdk.unmarkCommandFieldAsOptional },
|
|
56
|
-
generated: { mark: sdk.markCommandFieldAsGenerated, unmark: sdk.unmarkCommandFieldAsGenerated },
|
|
57
|
-
list: { mark: sdk.markCommandFieldAsList, unmark: sdk.unmarkCommandFieldAsList },
|
|
58
|
-
},
|
|
59
|
-
event: {
|
|
60
|
-
optional: { mark: sdk.markEventFieldAsOptional, unmark: sdk.unmarkEventFieldAsOptional },
|
|
61
|
-
generated: { mark: sdk.markEventFieldAsGenerated, unmark: sdk.unmarkEventFieldAsGenerated },
|
|
62
|
-
list: { mark: sdk.markEventFieldAsList, unmark: sdk.unmarkEventFieldAsList },
|
|
63
|
-
},
|
|
64
|
-
readmodel: {
|
|
65
|
-
optional: { mark: sdk.markReadModelFieldAsOptional, unmark: sdk.unmarkReadModelFieldAsOptional },
|
|
66
|
-
generated: { mark: sdk.markReadModelFieldAsGenerated, unmark: sdk.unmarkReadModelFieldAsGenerated },
|
|
67
|
-
list: { mark: sdk.markReadModelFieldAsList, unmark: sdk.unmarkReadModelFieldAsList },
|
|
68
|
-
},
|
|
69
|
-
screen: {
|
|
70
|
-
optional: { mark: sdk.markScreenFieldAsOptional, unmark: sdk.unmarkScreenFieldAsOptional },
|
|
71
|
-
generated: { mark: sdk.markScreenFieldAsGenerated, unmark: sdk.unmarkScreenFieldAsGenerated },
|
|
72
|
-
list: { mark: sdk.markScreenFieldAsList, unmark: sdk.unmarkScreenFieldAsList },
|
|
73
|
-
'user-input': { mark: sdk.markScreenFieldAsUserInput, unmark: sdk.unmarkScreenFieldAsUserInput },
|
|
74
|
-
},
|
|
75
|
-
processor: {
|
|
76
|
-
optional: { mark: sdk.markProcessorFieldAsOptional, unmark: sdk.unmarkProcessorFieldAsOptional },
|
|
77
|
-
generated: { mark: sdk.markProcessorFieldAsGenerated, unmark: sdk.unmarkProcessorFieldAsGenerated },
|
|
78
|
-
list: { mark: sdk.markProcessorFieldAsList, unmark: sdk.unmarkProcessorFieldAsList },
|
|
79
|
-
},
|
|
80
|
-
'external-event': {
|
|
81
|
-
optional: { mark: sdk.markExternalEventFieldAsOptional, unmark: sdk.unmarkExternalEventFieldAsOptional },
|
|
82
|
-
list: { mark: sdk.markExternalEventFieldAsList, unmark: sdk.unmarkExternalEventFieldAsList },
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
const buildSubfieldMarkMap = () => ({
|
|
86
|
-
command: {
|
|
87
|
-
optional: { mark: sdk.markCommandSubfieldAsOptional, unmark: sdk.unmarkCommandSubfieldAsOptional },
|
|
88
|
-
generated: { mark: sdk.markCommandSubfieldAsGenerated, unmark: sdk.unmarkCommandSubfieldAsGenerated },
|
|
89
|
-
list: { mark: sdk.markCommandSubfieldAsList, unmark: sdk.unmarkCommandSubfieldAsList },
|
|
90
|
-
},
|
|
91
|
-
event: {
|
|
92
|
-
optional: { mark: sdk.markEventSubfieldAsOptional, unmark: sdk.unmarkEventSubfieldAsOptional },
|
|
93
|
-
generated: { mark: sdk.markEventSubfieldAsGenerated, unmark: sdk.unmarkEventSubfieldAsGenerated },
|
|
94
|
-
list: { mark: sdk.markEventSubfieldAsList, unmark: sdk.unmarkEventSubfieldAsList },
|
|
95
|
-
},
|
|
96
|
-
readmodel: {
|
|
97
|
-
optional: { mark: sdk.markReadModelSubfieldAsOptional, unmark: sdk.unmarkReadModelSubfieldAsOptional },
|
|
98
|
-
generated: { mark: sdk.markReadModelSubfieldAsGenerated, unmark: sdk.unmarkReadModelSubfieldAsGenerated },
|
|
99
|
-
list: { mark: sdk.markReadModelSubfieldAsList, unmark: sdk.unmarkReadModelSubfieldAsList },
|
|
100
|
-
},
|
|
101
|
-
screen: {
|
|
102
|
-
optional: { mark: sdk.markScreenSubfieldAsOptional, unmark: sdk.unmarkScreenSubfieldAsOptional },
|
|
103
|
-
generated: { mark: sdk.markScreenSubfieldAsGenerated, unmark: sdk.unmarkScreenSubfieldAsGenerated },
|
|
104
|
-
list: { mark: sdk.markScreenSubfieldAsList, unmark: sdk.unmarkScreenSubfieldAsList },
|
|
105
|
-
'user-input': { mark: sdk.markScreenSubfieldAsUserInput, unmark: sdk.unmarkScreenSubfieldAsUserInput },
|
|
106
|
-
},
|
|
107
|
-
processor: {
|
|
108
|
-
optional: { mark: sdk.markProcessorSubfieldAsOptional, unmark: sdk.unmarkProcessorSubfieldAsOptional },
|
|
109
|
-
generated: { mark: sdk.markProcessorSubfieldAsGenerated, unmark: sdk.unmarkProcessorSubfieldAsGenerated },
|
|
110
|
-
list: { mark: sdk.markProcessorSubfieldAsList, unmark: sdk.unmarkProcessorSubfieldAsList },
|
|
111
|
-
},
|
|
112
|
-
'external-event': {
|
|
113
|
-
optional: { mark: sdk.markExternalEventSubfieldAsOptional, unmark: sdk.unmarkExternalEventSubfieldAsOptional },
|
|
114
|
-
list: { mark: sdk.markExternalEventSubfieldAsList, unmark: sdk.unmarkExternalEventSubfieldAsList },
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
export function registerUpdateCommands(program) {
|
|
118
|
-
const update = program.command('update').description('Update fields and subfields');
|
|
119
|
-
update.command('field [elementName]')
|
|
120
|
-
.description('Update field properties')
|
|
121
|
-
.requiredOption('--field <name>', 'Field name')
|
|
122
|
-
.option('--name <newName>', 'Rename the field')
|
|
123
|
-
.option('--type <newType>', 'Change the field type')
|
|
124
|
-
.option('--optional <bool>', 'Mark as optional (true/false)')
|
|
125
|
-
.option('--generated <bool>', 'Mark as generated (true/false)')
|
|
126
|
-
.option('--list <bool>', 'Mark as list (true/false)')
|
|
127
|
-
.option('--user-input <bool>', 'Mark as user input (true/false, screens only)')
|
|
128
|
-
.action(async (elementName, opts, cmd) => {
|
|
129
|
-
const modelId = requireModelId();
|
|
130
|
-
const { elementId, elementType } = await resolveAnyElement(modelId, elementName ?? '', getGlobalId());
|
|
131
|
-
const key = elementIdKey(elementType);
|
|
132
|
-
const fieldResult = unwrap(await sdk.resolveField({
|
|
133
|
-
query: { modelId, elementId, fieldName: opts.field }
|
|
134
|
-
}));
|
|
135
|
-
const fieldId = fieldResult.fieldId;
|
|
136
|
-
if (opts.name) {
|
|
137
|
-
const fn = RENAME_FIELD_MAP[elementType];
|
|
138
|
-
if (!fn)
|
|
139
|
-
throw new Error(`Cannot rename fields on type: ${elementType}`);
|
|
140
|
-
unwrap(await fn({ body: { modelId, [key]: elementId, fieldId, fieldName: opts.name } }));
|
|
141
|
-
console.log(`Renamed field to "${opts.name}".`);
|
|
142
|
-
}
|
|
143
|
-
if (opts.type) {
|
|
144
|
-
const fn = CHANGE_TYPE_MAP[elementType];
|
|
145
|
-
if (!fn)
|
|
146
|
-
throw new Error(`Cannot change field type on: ${elementType}`);
|
|
147
|
-
unwrap(await fn({ body: { modelId, [key]: elementId, fieldId, fieldType: opts.type } }));
|
|
148
|
-
console.log(`Changed field type to "${opts.type}".`);
|
|
149
|
-
}
|
|
150
|
-
const markMap = buildMarkMap();
|
|
151
|
-
for (const flag of ['optional', 'generated', 'list', 'user-input']) {
|
|
152
|
-
const value = opts[flag === 'user-input' ? 'userInput' : flag];
|
|
153
|
-
if (value === undefined)
|
|
154
|
-
continue;
|
|
155
|
-
const boolVal = value === 'true';
|
|
156
|
-
const entry = markMap[elementType]?.[flag];
|
|
157
|
-
if (!entry)
|
|
158
|
-
throw new Error(`Cannot set ${flag} on type: ${elementType}`);
|
|
159
|
-
const fn = boolVal ? entry.mark : entry.unmark;
|
|
160
|
-
unwrap(await fn({ body: { modelId, [key]: elementId, fieldId } }));
|
|
161
|
-
console.log(`${boolVal ? 'Marked' : 'Unmarked'} field as ${flag}.`);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
// update subfield [elementName] --subfield <subfieldId> [--name <newName>] [--type <newType>] [--list <bool>] ...
|
|
165
|
-
update.command('subfield [elementName]')
|
|
166
|
-
.description('Update subfield properties')
|
|
167
|
-
.requiredOption('--subfield <id>', 'Subfield ID (from show output)')
|
|
168
|
-
.option('--name <newName>', 'Rename the subfield')
|
|
169
|
-
.option('--type <newType>', 'Change the subfield type')
|
|
170
|
-
.option('--optional <bool>', 'Mark as optional (true/false)')
|
|
171
|
-
.option('--generated <bool>', 'Mark as generated (true/false)')
|
|
172
|
-
.option('--list <bool>', 'Mark as list (true/false)')
|
|
173
|
-
.option('--user-input <bool>', 'Mark as user input (true/false, screens only)')
|
|
174
|
-
.action(async (elementName, opts, cmd) => {
|
|
175
|
-
const modelId = requireModelId();
|
|
176
|
-
const { elementId, elementType } = await resolveAnyElement(modelId, elementName ?? '', getGlobalId());
|
|
177
|
-
const key = elementIdKey(elementType);
|
|
178
|
-
if (opts.name) {
|
|
179
|
-
const fn = RENAME_SUBFIELD_MAP[elementType];
|
|
180
|
-
if (!fn)
|
|
181
|
-
throw new Error(`Cannot rename subfields on type: ${elementType}`);
|
|
182
|
-
unwrap(await fn({ body: { modelId, [key]: elementId, subfieldId: opts.subfield, fieldName: opts.name } }));
|
|
183
|
-
console.log(`Renamed subfield to "${opts.name}".`);
|
|
184
|
-
}
|
|
185
|
-
if (opts.type) {
|
|
186
|
-
const fn = CHANGE_SUBFIELD_TYPE_MAP[elementType];
|
|
187
|
-
if (!fn)
|
|
188
|
-
throw new Error(`Cannot change subfield type on: ${elementType}`);
|
|
189
|
-
unwrap(await fn({ body: { modelId, [key]: elementId, subfieldId: opts.subfield, fieldType: opts.type } }));
|
|
190
|
-
console.log(`Changed subfield type to "${opts.type}".`);
|
|
191
|
-
}
|
|
192
|
-
const markMap = buildSubfieldMarkMap();
|
|
193
|
-
for (const flag of ['optional', 'generated', 'list', 'user-input']) {
|
|
194
|
-
const value = opts[flag === 'user-input' ? 'userInput' : flag];
|
|
195
|
-
if (value === undefined)
|
|
196
|
-
continue;
|
|
197
|
-
const boolVal = value === 'true';
|
|
198
|
-
const entry = markMap[elementType]?.[flag];
|
|
199
|
-
if (!entry)
|
|
200
|
-
throw new Error(`Cannot set ${flag} on type: ${elementType}`);
|
|
201
|
-
const fn = boolVal ? entry.mark : entry.unmark;
|
|
202
|
-
unwrap(await fn({ body: { modelId, [key]: elementId, subfieldId: opts.subfield } }));
|
|
203
|
-
console.log(`${boolVal ? 'Marked' : 'Unmarked'} subfield as ${flag}.`);
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
// update reorder [elementName] --field <name> --position <n>
|
|
207
|
-
update.command('reorder [elementName]')
|
|
208
|
-
.description('Reorder a field on an element')
|
|
209
|
-
.requiredOption('--field <name>', 'Field name')
|
|
210
|
-
.requiredOption('--position <n>', 'New position (0-based)', parseInt)
|
|
211
|
-
.action(async (elementName, opts, cmd) => {
|
|
212
|
-
const modelId = requireModelId();
|
|
213
|
-
const { elementId, elementType } = await resolveAnyElement(modelId, elementName ?? '', getGlobalId());
|
|
214
|
-
const fn = REORDER_FIELD_MAP[elementType];
|
|
215
|
-
if (!fn)
|
|
216
|
-
throw new Error(`Cannot reorder fields on type: ${elementType}`);
|
|
217
|
-
const fieldResult = unwrap(await sdk.resolveField({
|
|
218
|
-
query: { modelId, elementId, fieldName: opts.field }
|
|
219
|
-
}));
|
|
220
|
-
const fieldId = fieldResult.fieldId;
|
|
221
|
-
const key = elementIdKey(elementType);
|
|
222
|
-
unwrap(await fn({ body: { modelId, [key]: elementId, fieldId, position: opts.position } }));
|
|
223
|
-
console.log(`Moved field "${opts.field}" to position ${opts.position}.`);
|
|
224
|
-
});
|
|
225
|
-
// update reorder-subfield [elementName] --subfield <id> --position <n>
|
|
226
|
-
update.command('reorder-subfield [elementName]')
|
|
227
|
-
.description('Reorder a subfield within its parent')
|
|
228
|
-
.requiredOption('--subfield <id>', 'Subfield ID (from show output)')
|
|
229
|
-
.requiredOption('--position <n>', 'New position (0-based)', parseInt)
|
|
230
|
-
.action(async (elementName, opts, cmd) => {
|
|
231
|
-
const modelId = requireModelId();
|
|
232
|
-
const { elementId, elementType } = await resolveAnyElement(modelId, elementName ?? '', getGlobalId());
|
|
233
|
-
const fn = REORDER_SUBFIELD_MAP[elementType];
|
|
234
|
-
if (!fn)
|
|
235
|
-
throw new Error(`Cannot reorder subfields on type: ${elementType}`);
|
|
236
|
-
const key = elementIdKey(elementType);
|
|
237
|
-
unwrap(await fn({ body: { modelId, [key]: elementId, subfieldId: opts.subfield, position: opts.position } }));
|
|
238
|
-
console.log(`Moved subfield to position ${opts.position}.`);
|
|
239
|
-
});
|
|
240
|
-
}
|
package/dist/lib/auth.js
DELETED
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
import * as http from 'node:http';
|
|
2
|
-
import * as crypto from 'node:crypto';
|
|
3
|
-
import { exec } from 'node:child_process';
|
|
4
|
-
import { saveAuthTokens, clearAuthTokens, getAuthTokens, getKeycloakUrl } from './config';
|
|
5
|
-
const KEYCLOAK_CLIENT_ID = 'eventmodeler-cli';
|
|
6
|
-
const REDIRECT_URI = 'http://localhost:8787/callback';
|
|
7
|
-
/**
|
|
8
|
-
* Decode the payload of a JWT token (no signature verification - that's the server's job).
|
|
9
|
-
*/
|
|
10
|
-
function decodeJwtPayload(token) {
|
|
11
|
-
const parts = token.split('.');
|
|
12
|
-
if (parts.length !== 3)
|
|
13
|
-
throw new Error('Invalid JWT format');
|
|
14
|
-
const payload = Buffer.from(parts[1], 'base64url').toString('utf-8');
|
|
15
|
-
return JSON.parse(payload);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Generate PKCE code verifier and challenge.
|
|
19
|
-
*/
|
|
20
|
-
function generatePKCE() {
|
|
21
|
-
// Generate a random code verifier (43-128 characters)
|
|
22
|
-
const codeVerifier = crypto.randomBytes(32).toString('base64url');
|
|
23
|
-
// Generate code challenge using S256 method
|
|
24
|
-
const codeChallenge = crypto
|
|
25
|
-
.createHash('sha256')
|
|
26
|
-
.update(codeVerifier)
|
|
27
|
-
.digest('base64url');
|
|
28
|
-
return { codeVerifier, codeChallenge };
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Starts the OAuth flow using Keycloak PKCE.
|
|
32
|
-
* Opens browser and waits for the callback.
|
|
33
|
-
*/
|
|
34
|
-
export async function startAuthFlow() {
|
|
35
|
-
return new Promise((resolve) => {
|
|
36
|
-
const keycloakUrl = getKeycloakUrl();
|
|
37
|
-
// Generate PKCE values
|
|
38
|
-
const { codeVerifier, codeChallenge } = generatePKCE();
|
|
39
|
-
// Generate state for CSRF protection
|
|
40
|
-
const state = crypto.randomBytes(16).toString('hex');
|
|
41
|
-
// Create local server to receive callback
|
|
42
|
-
const server = http.createServer(async (req, res) => {
|
|
43
|
-
const url = new URL(req.url ?? '/', `http://localhost:8787`);
|
|
44
|
-
if (url.pathname === '/callback') {
|
|
45
|
-
const code = url.searchParams.get('code');
|
|
46
|
-
const returnedState = url.searchParams.get('state');
|
|
47
|
-
const error = url.searchParams.get('error');
|
|
48
|
-
if (error) {
|
|
49
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
50
|
-
res.end(renderHtml('Authentication Failed', `Error: ${error}`, false));
|
|
51
|
-
server.close();
|
|
52
|
-
resolve({ success: false, error });
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (returnedState !== state) {
|
|
56
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
57
|
-
res.end(renderHtml('Authentication Failed', 'Invalid state parameter. Please try again.', false));
|
|
58
|
-
server.close();
|
|
59
|
-
resolve({ success: false, error: 'Invalid state parameter' });
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (!code) {
|
|
63
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
64
|
-
res.end(renderHtml('Authentication Failed', 'No authorization code received.', false));
|
|
65
|
-
server.close();
|
|
66
|
-
resolve({ success: false, error: 'No authorization code' });
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
try {
|
|
70
|
-
// Exchange the code for tokens via Keycloak token endpoint (form-urlencoded)
|
|
71
|
-
const tokenUrl = `${keycloakUrl}/protocol/openid-connect/token`;
|
|
72
|
-
const body = new URLSearchParams({
|
|
73
|
-
client_id: KEYCLOAK_CLIENT_ID,
|
|
74
|
-
code,
|
|
75
|
-
code_verifier: codeVerifier,
|
|
76
|
-
grant_type: 'authorization_code',
|
|
77
|
-
redirect_uri: REDIRECT_URI,
|
|
78
|
-
});
|
|
79
|
-
const tokenResponse = await fetch(tokenUrl, {
|
|
80
|
-
method: 'POST',
|
|
81
|
-
headers: {
|
|
82
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
83
|
-
},
|
|
84
|
-
body: body.toString(),
|
|
85
|
-
});
|
|
86
|
-
if (!tokenResponse.ok) {
|
|
87
|
-
const errorData = await tokenResponse.json().catch(() => ({}));
|
|
88
|
-
throw new Error(errorData.error_description ?? errorData.error ?? `Token exchange failed: ${tokenResponse.status}`);
|
|
89
|
-
}
|
|
90
|
-
const tokenData = await tokenResponse.json();
|
|
91
|
-
// Extract user info from JWT claims
|
|
92
|
-
const claims = decodeJwtPayload(tokenData.access_token);
|
|
93
|
-
const expiresAt = Date.now() + (tokenData.expires_in ?? 3600) * 1000;
|
|
94
|
-
const tokens = {
|
|
95
|
-
accessToken: tokenData.access_token,
|
|
96
|
-
refreshToken: tokenData.refresh_token ?? '',
|
|
97
|
-
expiresAt,
|
|
98
|
-
userId: String(claims.sub ?? ''),
|
|
99
|
-
email: String(claims.email ?? ''),
|
|
100
|
-
};
|
|
101
|
-
saveAuthTokens(tokens);
|
|
102
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
103
|
-
res.end(renderHtml('Authentication Successful!', formatSuccessMessage(tokens.email), true));
|
|
104
|
-
server.close();
|
|
105
|
-
resolve({ success: true, tokens });
|
|
106
|
-
}
|
|
107
|
-
catch (err) {
|
|
108
|
-
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
109
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
110
|
-
res.end(renderHtml('Authentication Failed', errorMessage, false));
|
|
111
|
-
server.close();
|
|
112
|
-
resolve({ success: false, error: errorMessage });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
res.writeHead(404);
|
|
117
|
-
res.end('Not found');
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
server.listen(8787, () => {
|
|
121
|
-
// Build Keycloak authorization URL with PKCE
|
|
122
|
-
const params = new URLSearchParams({
|
|
123
|
-
client_id: KEYCLOAK_CLIENT_ID,
|
|
124
|
-
redirect_uri: REDIRECT_URI,
|
|
125
|
-
response_type: 'code',
|
|
126
|
-
state,
|
|
127
|
-
code_challenge: codeChallenge,
|
|
128
|
-
code_challenge_method: 'S256',
|
|
129
|
-
scope: 'openid email profile',
|
|
130
|
-
});
|
|
131
|
-
const authorizationUrl = `${keycloakUrl}/protocol/openid-connect/auth?${params}`;
|
|
132
|
-
console.log('\nOpening browser for authentication...');
|
|
133
|
-
// Open the browser
|
|
134
|
-
openBrowser(authorizationUrl);
|
|
135
|
-
});
|
|
136
|
-
// Timeout after 5 minutes
|
|
137
|
-
setTimeout(() => {
|
|
138
|
-
server.close();
|
|
139
|
-
resolve({ success: false, error: 'Authentication timed out' });
|
|
140
|
-
}, 5 * 60 * 1000);
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Refresh the access token using the refresh token.
|
|
145
|
-
*/
|
|
146
|
-
export async function refreshAccessToken() {
|
|
147
|
-
const tokens = getAuthTokens();
|
|
148
|
-
if (!tokens?.refreshToken) {
|
|
149
|
-
return { success: false, error: 'No refresh token available' };
|
|
150
|
-
}
|
|
151
|
-
const keycloakUrl = getKeycloakUrl();
|
|
152
|
-
const tokenUrl = `${keycloakUrl}/protocol/openid-connect/token`;
|
|
153
|
-
try {
|
|
154
|
-
const body = new URLSearchParams({
|
|
155
|
-
client_id: KEYCLOAK_CLIENT_ID,
|
|
156
|
-
refresh_token: tokens.refreshToken,
|
|
157
|
-
grant_type: 'refresh_token',
|
|
158
|
-
});
|
|
159
|
-
const response = await fetch(tokenUrl, {
|
|
160
|
-
method: 'POST',
|
|
161
|
-
headers: {
|
|
162
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
163
|
-
},
|
|
164
|
-
body: body.toString(),
|
|
165
|
-
});
|
|
166
|
-
if (!response.ok) {
|
|
167
|
-
const errorData = await response.json().catch(() => ({}));
|
|
168
|
-
throw new Error(errorData.error_description ?? errorData.error ?? `Token refresh failed: ${response.status}`);
|
|
169
|
-
}
|
|
170
|
-
const tokenData = await response.json();
|
|
171
|
-
// Extract user info from refreshed JWT
|
|
172
|
-
const claims = decodeJwtPayload(tokenData.access_token);
|
|
173
|
-
const newTokens = {
|
|
174
|
-
accessToken: tokenData.access_token,
|
|
175
|
-
refreshToken: tokenData.refresh_token ?? tokens.refreshToken,
|
|
176
|
-
expiresAt: Date.now() + (tokenData.expires_in ?? 3600) * 1000,
|
|
177
|
-
userId: String(claims.sub ?? tokens.userId),
|
|
178
|
-
email: String(claims.email ?? tokens.email),
|
|
179
|
-
};
|
|
180
|
-
saveAuthTokens(newTokens);
|
|
181
|
-
return { success: true, tokens: newTokens };
|
|
182
|
-
}
|
|
183
|
-
catch (err) {
|
|
184
|
-
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
185
|
-
return { success: false, error: errorMessage };
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Get a valid access token, refreshing if needed.
|
|
190
|
-
*/
|
|
191
|
-
export async function getValidAccessToken() {
|
|
192
|
-
const tokens = getAuthTokens();
|
|
193
|
-
if (!tokens)
|
|
194
|
-
return null;
|
|
195
|
-
// If token is expired or will expire in 5 minutes, refresh it
|
|
196
|
-
if (tokens.expiresAt < Date.now() + 5 * 60 * 1000) {
|
|
197
|
-
const result = await refreshAccessToken();
|
|
198
|
-
if (!result.success || !result.tokens)
|
|
199
|
-
return null;
|
|
200
|
-
return result.tokens.accessToken;
|
|
201
|
-
}
|
|
202
|
-
return tokens.accessToken;
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Log out by clearing stored tokens.
|
|
206
|
-
*/
|
|
207
|
-
export function logout() {
|
|
208
|
-
clearAuthTokens();
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Open a URL in the default browser.
|
|
212
|
-
*/
|
|
213
|
-
function openBrowser(url) {
|
|
214
|
-
const platform = process.platform;
|
|
215
|
-
let command;
|
|
216
|
-
if (platform === 'darwin') {
|
|
217
|
-
command = `open "${url}"`;
|
|
218
|
-
}
|
|
219
|
-
else if (platform === 'win32') {
|
|
220
|
-
command = `start "" "${url}"`;
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
command = `xdg-open "${url}"`;
|
|
224
|
-
}
|
|
225
|
-
exec(command, (err) => {
|
|
226
|
-
if (err) {
|
|
227
|
-
console.error('Failed to open browser:', err.message);
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Render a styled HTML page for the callback matching Event Modeler's design.
|
|
233
|
-
*/
|
|
234
|
-
function renderHtml(title, message, success) {
|
|
235
|
-
const iconColor = success ? '#16a34a' : '#dc2626';
|
|
236
|
-
const icon = success
|
|
237
|
-
? `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>`
|
|
238
|
-
: `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>`;
|
|
239
|
-
return `<!DOCTYPE html>
|
|
240
|
-
<html lang="en">
|
|
241
|
-
<head>
|
|
242
|
-
<meta charset="UTF-8">
|
|
243
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
244
|
-
<title>${title} - Event Modeler</title>
|
|
245
|
-
<style>
|
|
246
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
247
|
-
body {
|
|
248
|
-
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
249
|
-
display: flex;
|
|
250
|
-
flex-direction: column;
|
|
251
|
-
justify-content: center;
|
|
252
|
-
align-items: center;
|
|
253
|
-
min-height: 100vh;
|
|
254
|
-
background: #fafaf9;
|
|
255
|
-
color: #57534e;
|
|
256
|
-
padding: 1rem;
|
|
257
|
-
}
|
|
258
|
-
.card {
|
|
259
|
-
background: white;
|
|
260
|
-
border: 1px solid #e7e5e4;
|
|
261
|
-
border-radius: 12px;
|
|
262
|
-
padding: 2.5rem 3rem;
|
|
263
|
-
text-align: center;
|
|
264
|
-
max-width: 420px;
|
|
265
|
-
width: 100%;
|
|
266
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
267
|
-
}
|
|
268
|
-
.icon {
|
|
269
|
-
margin-bottom: 1.5rem;
|
|
270
|
-
}
|
|
271
|
-
.logo {
|
|
272
|
-
display: flex;
|
|
273
|
-
align-items: center;
|
|
274
|
-
justify-content: center;
|
|
275
|
-
gap: 0.5rem;
|
|
276
|
-
margin-bottom: 2rem;
|
|
277
|
-
font-weight: 600;
|
|
278
|
-
font-size: 1.125rem;
|
|
279
|
-
color: #44403c;
|
|
280
|
-
}
|
|
281
|
-
.logo svg {
|
|
282
|
-
width: 28px;
|
|
283
|
-
height: 28px;
|
|
284
|
-
}
|
|
285
|
-
h1 {
|
|
286
|
-
font-size: 1.5rem;
|
|
287
|
-
font-weight: 600;
|
|
288
|
-
color: #1c1917;
|
|
289
|
-
margin-bottom: 0.5rem;
|
|
290
|
-
}
|
|
291
|
-
.message {
|
|
292
|
-
color: #78716c;
|
|
293
|
-
margin-bottom: 1.5rem;
|
|
294
|
-
line-height: 1.5;
|
|
295
|
-
}
|
|
296
|
-
.hint {
|
|
297
|
-
font-size: 0.875rem;
|
|
298
|
-
color: #a8a29e;
|
|
299
|
-
padding-top: 1.5rem;
|
|
300
|
-
border-top: 1px solid #f5f5f4;
|
|
301
|
-
}
|
|
302
|
-
.email {
|
|
303
|
-
font-weight: 500;
|
|
304
|
-
color: #44403c;
|
|
305
|
-
}
|
|
306
|
-
</style>
|
|
307
|
-
</head>
|
|
308
|
-
<body>
|
|
309
|
-
<div class="card">
|
|
310
|
-
<div class="logo">
|
|
311
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
312
|
-
<path d="M50 50 L50 5 A45 45 0 0 1 89.0 72.5 Z" fill="#D5F1A5"/>
|
|
313
|
-
<path d="M50 50 L89.0 72.5 A45 45 0 0 1 11.0 72.5 Z" fill="#FFB87B"/>
|
|
314
|
-
<path d="M50 50 L11.0 72.5 A45 45 0 0 1 50 5 Z" fill="#B7D3FE"/>
|
|
315
|
-
</svg>
|
|
316
|
-
Event Modeler
|
|
317
|
-
</div>
|
|
318
|
-
<div class="icon">${icon}</div>
|
|
319
|
-
<h1>${title}</h1>
|
|
320
|
-
<p class="message">${message}</p>
|
|
321
|
-
<p class="hint">You can close this window and return to the terminal.</p>
|
|
322
|
-
</div>
|
|
323
|
-
</body>
|
|
324
|
-
</html>`;
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Format the success message with email highlighted.
|
|
328
|
-
*/
|
|
329
|
-
function formatSuccessMessage(email) {
|
|
330
|
-
return `Signed in as <span class="email">${email}</span>`;
|
|
331
|
-
}
|
package/dist/lib/config.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import * as os from 'node:os';
|
|
4
|
-
const CONFIG_DIR = path.join(os.homedir(), '.eventmodeler');
|
|
5
|
-
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
6
|
-
export function getConfigDir() {
|
|
7
|
-
return CONFIG_DIR;
|
|
8
|
-
}
|
|
9
|
-
export function getConfigPath() {
|
|
10
|
-
return CONFIG_PATH;
|
|
11
|
-
}
|
|
12
|
-
export function ensureConfigDir() {
|
|
13
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
14
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
export function loadGlobalConfig() {
|
|
18
|
-
try {
|
|
19
|
-
if (fs.existsSync(CONFIG_PATH)) {
|
|
20
|
-
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
// Ignore errors, return empty config
|
|
25
|
-
}
|
|
26
|
-
return {};
|
|
27
|
-
}
|
|
28
|
-
export function saveGlobalConfig(config) {
|
|
29
|
-
ensureConfigDir();
|
|
30
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
31
|
-
}
|
|
32
|
-
export function getAuthTokens() {
|
|
33
|
-
const config = loadGlobalConfig();
|
|
34
|
-
return config.auth ?? null;
|
|
35
|
-
}
|
|
36
|
-
export function saveAuthTokens(tokens) {
|
|
37
|
-
const config = loadGlobalConfig();
|
|
38
|
-
config.auth = tokens;
|
|
39
|
-
saveGlobalConfig(config);
|
|
40
|
-
}
|
|
41
|
-
export function clearAuthTokens() {
|
|
42
|
-
const config = loadGlobalConfig();
|
|
43
|
-
delete config.auth;
|
|
44
|
-
saveGlobalConfig(config);
|
|
45
|
-
}
|
|
46
|
-
export function isAuthenticated() {
|
|
47
|
-
const tokens = getAuthTokens();
|
|
48
|
-
if (!tokens)
|
|
49
|
-
return false;
|
|
50
|
-
// Check if token is expired (with 5 minute buffer)
|
|
51
|
-
return tokens.expiresAt > Date.now() + 5 * 60 * 1000;
|
|
52
|
-
}
|
|
53
|
-
export function getBackendUrl() {
|
|
54
|
-
const config = loadGlobalConfig();
|
|
55
|
-
if (config.backendUrl)
|
|
56
|
-
return config.backendUrl;
|
|
57
|
-
if (process.env.EVENTMODELER_BACKEND_URL)
|
|
58
|
-
return process.env.EVENTMODELER_BACKEND_URL;
|
|
59
|
-
// Default to production unless development mode is enabled
|
|
60
|
-
return config.development ? 'http://localhost:8080' : 'https://api.eventmodeler.com';
|
|
61
|
-
}
|
|
62
|
-
export function setBackendUrl(url) {
|
|
63
|
-
const config = loadGlobalConfig();
|
|
64
|
-
config.backendUrl = url;
|
|
65
|
-
saveGlobalConfig(config);
|
|
66
|
-
}
|
|
67
|
-
export function getKeycloakUrl() {
|
|
68
|
-
const config = loadGlobalConfig();
|
|
69
|
-
if (config.keycloakUrl)
|
|
70
|
-
return config.keycloakUrl;
|
|
71
|
-
if (process.env.EVENTMODELER_KEYCLOAK_URL)
|
|
72
|
-
return process.env.EVENTMODELER_KEYCLOAK_URL;
|
|
73
|
-
// Default to production unless development mode is enabled
|
|
74
|
-
return config.development ? 'http://localhost:18180/realms/eventmodeler' : 'https://auth.eventmodeler.com/realms/eventmodeler';
|
|
75
|
-
}
|
|
76
|
-
export function setKeycloakUrl(url) {
|
|
77
|
-
const config = loadGlobalConfig();
|
|
78
|
-
config.keycloakUrl = url;
|
|
79
|
-
saveGlobalConfig(config);
|
|
80
|
-
}
|