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.
Files changed (73) hide show
  1. package/dist/index.js +7133 -34
  2. package/package.json +5 -4
  3. package/dist/api/client-config.js +0 -10
  4. package/dist/api/generated/client/client.gen.js +0 -235
  5. package/dist/api/generated/client/index.js +0 -6
  6. package/dist/api/generated/client/types.gen.js +0 -2
  7. package/dist/api/generated/client/utils.gen.js +0 -228
  8. package/dist/api/generated/client.gen.js +0 -4
  9. package/dist/api/generated/core/auth.gen.js +0 -14
  10. package/dist/api/generated/core/bodySerializer.gen.js +0 -57
  11. package/dist/api/generated/core/params.gen.js +0 -100
  12. package/dist/api/generated/core/pathSerializer.gen.js +0 -106
  13. package/dist/api/generated/core/queryKeySerializer.gen.js +0 -92
  14. package/dist/api/generated/core/serverSentEvents.gen.js +0 -133
  15. package/dist/api/generated/core/types.gen.js +0 -2
  16. package/dist/api/generated/core/utils.gen.js +0 -87
  17. package/dist/api/generated/index.js +0 -2
  18. package/dist/api/generated/sdk.gen.js +0 -4222
  19. package/dist/api/generated/types.gen.js +0 -2
  20. package/dist/api/generated/zod.gen.js +0 -7217
  21. package/dist/commands/add.js +0 -315
  22. package/dist/commands/auth.js +0 -14
  23. package/dist/commands/create.js +0 -192
  24. package/dist/commands/design.js +0 -108
  25. package/dist/commands/guide.js +0 -15
  26. package/dist/commands/init.js +0 -21
  27. package/dist/commands/list-schemas.js +0 -177
  28. package/dist/commands/list.js +0 -39
  29. package/dist/commands/loop.js +0 -101
  30. package/dist/commands/map.js +0 -40
  31. package/dist/commands/mark.js +0 -27
  32. package/dist/commands/move.js +0 -35
  33. package/dist/commands/remove.js +0 -170
  34. package/dist/commands/rename.js +0 -53
  35. package/dist/commands/resize.js +0 -30
  36. package/dist/commands/search.js +0 -14
  37. package/dist/commands/set.js +0 -199
  38. package/dist/commands/show-schemas.js +0 -259
  39. package/dist/commands/show.js +0 -56
  40. package/dist/commands/summary.js +0 -13
  41. package/dist/commands/update.js +0 -240
  42. package/dist/lib/auth.js +0 -331
  43. package/dist/lib/config.js +0 -80
  44. package/dist/lib/excalidraw-schema.js +0 -66
  45. package/dist/lib/globals.js +0 -8
  46. package/dist/lib/model.js +0 -11
  47. package/dist/lib/project-config.js +0 -103
  48. package/dist/lib/resolve.js +0 -59
  49. package/dist/lib/scenario.js +0 -15
  50. package/dist/slices/add-scenario/index.js +0 -103
  51. package/dist/slices/guide/guides/codegen.js +0 -339
  52. package/dist/slices/guide/guides/connect-slices.js +0 -202
  53. package/dist/slices/guide/guides/create-slices.js +0 -273
  54. package/dist/slices/guide/guides/explore.js +0 -238
  55. package/dist/slices/guide/guides/information-flow.js +0 -304
  56. package/dist/slices/guide/guides/scenarios.js +0 -214
  57. package/dist/slices/guide/index.js +0 -40
  58. package/dist/slices/help/index.js +0 -96
  59. package/dist/slices/help/topics/build-codegen.js +0 -109
  60. package/dist/slices/help/topics/build-slice.js +0 -147
  61. package/dist/slices/help/topics/check-completeness.js +0 -57
  62. package/dist/slices/help/topics/connect-slices.js +0 -99
  63. package/dist/slices/help/topics/explore-model.js +0 -112
  64. package/dist/slices/help/topics/json-reference.js +0 -188
  65. package/dist/slices/help/topics/linked-copies.js +0 -89
  66. package/dist/slices/help/topics/manipulate-canvas.js +0 -150
  67. package/dist/slices/help/topics/write-scenarios.js +0 -162
  68. package/dist/slices/init/index.js +0 -86
  69. package/dist/slices/init/loop.js +0 -60
  70. package/dist/slices/login/index.js +0 -20
  71. package/dist/slices/logout/index.js +0 -14
  72. package/dist/slices/open-app/index.js +0 -36
  73. package/dist/slices/whoami/index.js +0 -19
@@ -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
- }
@@ -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
- }