@camunda8/cli 2.0.0-alpha.1 → 2.0.0-alpha.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/config.js CHANGED
@@ -1,16 +1,108 @@
1
1
  /**
2
2
  * Configuration and session state management for c8ctl
3
- * Handles profiles, session state, and credential resolution
3
+ *
4
+ * c8ctl stores its own profiles in DATA_DIR/c8ctl/profiles.json
5
+ * Modeler connections are read from settings.json (read-only) with "modeler:" prefix
4
6
  */
5
7
  import { homedir, platform } from 'node:os';
6
8
  import { join } from 'node:path';
7
9
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
8
10
  import { c8ctl } from "./runtime.js";
11
+ // ============================================================================
12
+ // Constants - matching Camunda Modeler exactly
13
+ // ============================================================================
14
+ export const TARGET_TYPES = {
15
+ CAMUNDA_CLOUD: 'camundaCloud',
16
+ SELF_HOSTED: 'selfHosted',
17
+ };
18
+ export const AUTH_TYPES = {
19
+ NONE: 'none',
20
+ BASIC: 'basic',
21
+ OAUTH: 'oauth',
22
+ };
23
+ // ============================================================================
24
+ // Validation - matching Camunda Modeler's validation rules
25
+ // ============================================================================
26
+ const VALIDATION_PATTERNS = {
27
+ URL: /^(http|grpc)s?:\/\//,
28
+ CAMUNDA_CLOUD_GRPC_URL: /^((https|grpcs):\/\/|)[a-z\d-]+\.[a-z]+-\d+\.zeebe\.camunda\.io(:443|)\/?$/,
29
+ CAMUNDA_CLOUD_REST_URL: /^https:\/\/[a-z]+-\d+\.zeebe\.camunda\.io(:443|)\/[a-z\d-]+\/?$/,
30
+ };
31
+ // Combined pattern for cloud URLs
32
+ const CAMUNDA_CLOUD_URL_PATTERN = new RegExp(`${VALIDATION_PATTERNS.CAMUNDA_CLOUD_GRPC_URL.source}|${VALIDATION_PATTERNS.CAMUNDA_CLOUD_REST_URL.source}`);
33
+ /**
34
+ * Validate a connection configuration
35
+ * Returns array of error messages (empty if valid)
36
+ */
37
+ export function validateConnection(conn) {
38
+ const errors = [];
39
+ if (!conn) {
40
+ errors.push('Connection configuration is required');
41
+ return errors;
42
+ }
43
+ if (!conn.id) {
44
+ errors.push('Connection must have an ID');
45
+ }
46
+ if (!conn.targetType) {
47
+ errors.push('Target type is required (camundaCloud or selfHosted)');
48
+ return errors;
49
+ }
50
+ if (conn.targetType === TARGET_TYPES.CAMUNDA_CLOUD) {
51
+ if (!conn.camundaCloudClusterUrl) {
52
+ errors.push('Cluster URL is required for Camunda Cloud');
53
+ }
54
+ else if (!CAMUNDA_CLOUD_URL_PATTERN.test(conn.camundaCloudClusterUrl)) {
55
+ errors.push('Cluster URL must be a valid Camunda 8 SaaS URL');
56
+ }
57
+ if (!conn.camundaCloudClientId) {
58
+ errors.push('Client ID is required for Camunda Cloud');
59
+ }
60
+ if (!conn.camundaCloudClientSecret) {
61
+ errors.push('Client Secret is required for Camunda Cloud');
62
+ }
63
+ }
64
+ else if (conn.targetType === TARGET_TYPES.SELF_HOSTED) {
65
+ if (!conn.contactPoint) {
66
+ errors.push('Cluster URL (contactPoint) is required for Self-Hosted');
67
+ }
68
+ else if (!VALIDATION_PATTERNS.URL.test(conn.contactPoint)) {
69
+ errors.push('Cluster URL must start with http://, https://, grpc://, or grpcs://');
70
+ }
71
+ if (conn.authType === AUTH_TYPES.BASIC) {
72
+ if (!conn.basicAuthUsername) {
73
+ errors.push('Username is required for Basic authentication');
74
+ }
75
+ if (!conn.basicAuthPassword) {
76
+ errors.push('Password is required for Basic authentication');
77
+ }
78
+ }
79
+ else if (conn.authType === AUTH_TYPES.OAUTH) {
80
+ if (!conn.clientId) {
81
+ errors.push('Client ID is required for OAuth authentication');
82
+ }
83
+ if (!conn.clientSecret) {
84
+ errors.push('Client Secret is required for OAuth authentication');
85
+ }
86
+ if (!conn.oauthURL) {
87
+ errors.push('OAuth URL is required for OAuth authentication');
88
+ }
89
+ if (!conn.audience) {
90
+ errors.push('Audience is required for OAuth authentication');
91
+ }
92
+ }
93
+ }
94
+ else {
95
+ errors.push(`Unknown target type: ${conn.targetType}`);
96
+ }
97
+ return errors;
98
+ }
99
+ // ============================================================================
100
+ // Directory and Path Utilities
101
+ // ============================================================================
9
102
  /**
10
- * Get platform-specific user data directory
103
+ * Get platform-specific user data directory for c8ctl
11
104
  */
12
105
  export function getUserDataDir() {
13
- // Allow override for testing
14
106
  if (process.env.C8CTL_DATA_DIR) {
15
107
  return process.env.C8CTL_DATA_DIR;
16
108
  }
@@ -21,14 +113,19 @@ export function getUserDataDir() {
21
113
  return join(process.env.APPDATA || join(home, 'AppData', 'Roaming'), 'c8ctl');
22
114
  case 'darwin':
23
115
  return join(home, 'Library', 'Application Support', 'c8ctl');
24
- default: // linux and others
25
- return join(process.env.XDG_DATA_HOME || join(home, '.local', 'share'), 'c8ctl');
116
+ default:
117
+ return join(process.env.XDG_CONFIG_HOME || join(home, '.config'), 'c8ctl');
26
118
  }
27
119
  }
28
120
  /**
29
121
  * Get platform-specific Camunda Modeler data directory
122
+ * Modeler stores connections in settings.json
30
123
  */
31
124
  export function getModelerDataDir() {
125
+ // Allow override for testing
126
+ if (process.env.C8CTL_MODELER_DIR) {
127
+ return process.env.C8CTL_MODELER_DIR;
128
+ }
32
129
  const plat = platform();
33
130
  const home = homedir();
34
131
  switch (plat) {
@@ -36,13 +133,10 @@ export function getModelerDataDir() {
36
133
  return join(process.env.APPDATA || join(home, 'AppData', 'Roaming'), 'camunda-modeler');
37
134
  case 'darwin':
38
135
  return join(home, 'Library', 'Application Support', 'camunda-modeler');
39
- default: // linux and others
136
+ default:
40
137
  return join(process.env.XDG_CONFIG_HOME || join(home, '.config'), 'camunda-modeler');
41
138
  }
42
139
  }
43
- /**
44
- * Ensure user data directory exists
45
- */
46
140
  function ensureUserDataDir() {
47
141
  const dir = getUserDataDir();
48
142
  if (!existsSync(dir)) {
@@ -50,63 +144,53 @@ function ensureUserDataDir() {
50
144
  }
51
145
  return dir;
52
146
  }
53
- /**
54
- * Get profiles file path
55
- */
147
+ function getSessionStatePath() {
148
+ return join(ensureUserDataDir(), 'session.json');
149
+ }
56
150
  function getProfilesPath() {
57
151
  return join(ensureUserDataDir(), 'profiles.json');
58
152
  }
59
- /**
60
- * Get session state file path
61
- */
62
- function getSessionStatePath() {
63
- return join(ensureUserDataDir(), 'session.json');
153
+ function getModelerSettingsPath() {
154
+ return join(getModelerDataDir(), 'settings.json');
64
155
  }
65
156
  /**
66
- * Load all profiles
157
+ * Load c8ctl profiles from profiles.json
67
158
  */
68
159
  export function loadProfiles() {
69
- const path = getProfilesPath();
70
- if (!existsSync(path)) {
160
+ const profilesPath = getProfilesPath();
161
+ if (!existsSync(profilesPath)) {
71
162
  return [];
72
163
  }
73
164
  try {
74
- const data = readFileSync(path, 'utf-8');
75
- return JSON.parse(data);
165
+ const data = readFileSync(profilesPath, 'utf-8');
166
+ const profilesFile = JSON.parse(data);
167
+ return profilesFile.profiles || [];
76
168
  }
77
- catch (error) {
169
+ catch {
78
170
  return [];
79
171
  }
80
172
  }
81
173
  /**
82
- * Save profiles to disk
174
+ * Save c8ctl profiles to profiles.json
83
175
  */
84
176
  export function saveProfiles(profiles) {
85
- const path = getProfilesPath();
86
- writeFileSync(path, JSON.stringify(profiles, null, 2), 'utf-8');
177
+ const profilesPath = getProfilesPath();
178
+ const profilesFile = { profiles };
179
+ writeFileSync(profilesPath, JSON.stringify(profilesFile, null, 2), 'utf-8');
87
180
  }
88
181
  /**
89
- * Get a profile by name
90
- * Supports both c8ctl profiles and modeler profiles (with 'modeler:' prefix)
182
+ * Get a c8ctl profile by name
91
183
  */
92
184
  export function getProfile(name) {
93
- // Check if this is a modeler profile request
94
- if (name.startsWith('modeler:')) {
95
- const modelerProfile = getModelerProfile(name);
96
- if (modelerProfile) {
97
- return convertModelerProfile(modelerProfile);
98
- }
99
- return undefined;
100
- }
101
- // Check c8ctl profiles
102
185
  const profiles = loadProfiles();
103
186
  return profiles.find(p => p.name === name);
104
187
  }
105
188
  /**
106
- * Add or update a profile
189
+ * Add a c8ctl profile
107
190
  */
108
191
  export function addProfile(profile) {
109
192
  const profiles = loadProfiles();
193
+ // Check if profile already exists
110
194
  const existingIndex = profiles.findIndex(p => p.name === profile.name);
111
195
  if (existingIndex >= 0) {
112
196
  profiles[existingIndex] = profile;
@@ -117,25 +201,192 @@ export function addProfile(profile) {
117
201
  saveProfiles(profiles);
118
202
  }
119
203
  /**
120
- * Remove a profile
204
+ * Remove a c8ctl profile by name
121
205
  */
122
206
  export function removeProfile(name) {
123
207
  const profiles = loadProfiles();
124
208
  const filtered = profiles.filter(p => p.name !== name);
125
209
  if (filtered.length === profiles.length) {
126
- return false; // Profile not found
210
+ return false;
127
211
  }
128
212
  saveProfiles(filtered);
129
213
  return true;
130
214
  }
215
+ // ============================================================================
216
+ // Modeler Connection Management - READ-ONLY from settings.json
217
+ // ============================================================================
218
+ export const MODELER_PREFIX = 'modeler:';
219
+ /**
220
+ * Load connections from Modeler's settings.json (read-only)
221
+ * These are NOT modified by c8ctl
222
+ */
223
+ export function loadModelerConnections() {
224
+ const settingsPath = getModelerSettingsPath();
225
+ if (!existsSync(settingsPath)) {
226
+ return [];
227
+ }
228
+ try {
229
+ const data = readFileSync(settingsPath, 'utf-8');
230
+ const settings = JSON.parse(data);
231
+ const connections = settings['connectionManagerPlugin.c8connections'];
232
+ if (!connections || !Array.isArray(connections)) {
233
+ return [];
234
+ }
235
+ // Filter out invalid connections (must have id)
236
+ return connections.filter(c => c && c.id);
237
+ }
238
+ catch {
239
+ return [];
240
+ }
241
+ }
242
+ /**
243
+ * Get all profiles including c8ctl profiles and Modeler connections
244
+ * Modeler connections are prefixed with "modeler:"
245
+ */
246
+ export function getAllProfiles() {
247
+ const c8ctlProfiles = loadProfiles();
248
+ const modelerConnections = loadModelerConnections();
249
+ // Convert Modeler connections to Profile format with "modeler:" prefix
250
+ const modelerProfiles = modelerConnections.map(connectionToProfile).map(p => ({
251
+ ...p,
252
+ name: `${MODELER_PREFIX}${p.name}`,
253
+ }));
254
+ return [...c8ctlProfiles, ...modelerProfiles];
255
+ }
256
+ /**
257
+ * Get a profile by name, checking both c8ctl and Modeler sources
258
+ * For Modeler profiles, accepts name with or without "modeler:" prefix
259
+ */
260
+ export function getProfileOrModeler(name) {
261
+ // Try c8ctl profiles first
262
+ const c8ctlProfile = getProfile(name);
263
+ if (c8ctlProfile) {
264
+ return c8ctlProfile;
265
+ }
266
+ // Try Modeler connections (with or without prefix)
267
+ const modelerName = name.startsWith(MODELER_PREFIX) ? name.slice(MODELER_PREFIX.length) : name;
268
+ const modelerConnections = loadModelerConnections();
269
+ const modelerConnection = modelerConnections.find(c => c.name === modelerName || c.id === modelerName);
270
+ if (modelerConnection) {
271
+ const profile = connectionToProfile(modelerConnection);
272
+ return {
273
+ ...profile,
274
+ name: `${MODELER_PREFIX}${profile.name}`,
275
+ };
276
+ }
277
+ return undefined;
278
+ }
279
+ // ============================================================================
280
+ // Conversion Utilities
281
+ // ============================================================================
282
+ /**
283
+ * Convert a Connection to ClusterConfig for API client use
284
+ */
285
+ export function connectionToClusterConfig(conn) {
286
+ if (conn.targetType === TARGET_TYPES.CAMUNDA_CLOUD) {
287
+ return {
288
+ baseUrl: conn.camundaCloudClusterUrl || '',
289
+ clientId: conn.camundaCloudClientId,
290
+ clientSecret: conn.camundaCloudClientSecret,
291
+ audience: conn.camundaCloudClusterUrl, // Cloud uses URL as audience
292
+ oAuthUrl: 'https://login.cloud.camunda.io/oauth/token',
293
+ };
294
+ }
295
+ // Self-hosted
296
+ const config = {
297
+ baseUrl: conn.contactPoint || 'http://localhost:8080/v2',
298
+ };
299
+ if (conn.authType === AUTH_TYPES.BASIC) {
300
+ config.username = conn.basicAuthUsername;
301
+ config.password = conn.basicAuthPassword;
302
+ }
303
+ else if (conn.authType === AUTH_TYPES.OAUTH) {
304
+ config.clientId = conn.clientId;
305
+ config.clientSecret = conn.clientSecret;
306
+ config.oAuthUrl = conn.oauthURL;
307
+ config.audience = conn.audience;
308
+ }
309
+ return config;
310
+ }
311
+ /**
312
+ * Convert Connection to Profile format
313
+ * Used to convert read-only Modeler connections to c8ctl Profile format
314
+ */
315
+ export function connectionToProfile(conn) {
316
+ const config = connectionToClusterConfig(conn);
317
+ return {
318
+ name: conn.name || conn.id,
319
+ baseUrl: config.baseUrl,
320
+ clientId: config.clientId,
321
+ clientSecret: config.clientSecret,
322
+ audience: config.audience,
323
+ oAuthUrl: config.oAuthUrl,
324
+ username: config.username,
325
+ password: config.password,
326
+ defaultTenantId: conn.tenantId,
327
+ };
328
+ }
329
+ /**
330
+ * Convert Profile to ClusterConfig for API client use
331
+ */
332
+ export function profileToClusterConfig(profile) {
333
+ return {
334
+ baseUrl: profile.baseUrl,
335
+ clientId: profile.clientId,
336
+ clientSecret: profile.clientSecret,
337
+ audience: profile.audience,
338
+ oAuthUrl: profile.oAuthUrl,
339
+ username: profile.username,
340
+ password: profile.password,
341
+ };
342
+ }
343
+ /**
344
+ * Get display label for a connection
345
+ */
346
+ export function getConnectionLabel(conn) {
347
+ if (conn.name) {
348
+ return conn.name;
349
+ }
350
+ // Fallback to URL-based label like Modeler does
351
+ const url = conn.targetType === TARGET_TYPES.CAMUNDA_CLOUD
352
+ ? conn.camundaCloudClusterUrl
353
+ : conn.contactPoint;
354
+ return url ? `Unnamed (${url})` : 'Unnamed connection';
355
+ }
356
+ /**
357
+ * Get auth type label for display
358
+ */
359
+ export function getAuthTypeLabel(conn) {
360
+ if (conn.targetType === TARGET_TYPES.CAMUNDA_CLOUD) {
361
+ return 'OAuth (Cloud)';
362
+ }
363
+ switch (conn.authType) {
364
+ case AUTH_TYPES.BASIC:
365
+ return 'Basic';
366
+ case AUTH_TYPES.OAUTH:
367
+ return 'OAuth';
368
+ case AUTH_TYPES.NONE:
369
+ default:
370
+ return 'None';
371
+ }
372
+ }
373
+ /**
374
+ * Get target type label for display
375
+ */
376
+ export function getTargetTypeLabel(conn) {
377
+ return conn.targetType === TARGET_TYPES.CAMUNDA_CLOUD
378
+ ? 'Camunda Cloud'
379
+ : 'Self-Hosted';
380
+ }
381
+ // ============================================================================
382
+ // Session State Management
383
+ // ============================================================================
131
384
  /**
132
385
  * Load session state from disk and populate c8ctl runtime object
133
- * Loads all session properties (activeProfile, activeTenant, outputMode)
134
386
  */
135
387
  export function loadSessionState() {
136
388
  const path = getSessionStatePath();
137
389
  if (!existsSync(path)) {
138
- // Return default state if no session file exists
139
390
  return {
140
391
  activeProfile: c8ctl.activeProfile,
141
392
  activeTenant: c8ctl.activeTenant,
@@ -145,7 +396,6 @@ export function loadSessionState() {
145
396
  try {
146
397
  const data = readFileSync(path, 'utf-8');
147
398
  const state = JSON.parse(data);
148
- // Populate c8ctl runtime with loaded state (convert null to undefined)
149
399
  c8ctl.activeProfile = state.activeProfile === null ? undefined : state.activeProfile;
150
400
  c8ctl.activeTenant = state.activeTenant === null ? undefined : state.activeTenant;
151
401
  c8ctl.outputMode = state.outputMode || 'text';
@@ -155,8 +405,7 @@ export function loadSessionState() {
155
405
  outputMode: c8ctl.outputMode,
156
406
  };
157
407
  }
158
- catch (error) {
159
- // Return current state if file is corrupted
408
+ catch {
160
409
  return {
161
410
  activeProfile: c8ctl.activeProfile,
162
411
  activeTenant: c8ctl.activeTenant,
@@ -166,7 +415,6 @@ export function loadSessionState() {
166
415
  }
167
416
  /**
168
417
  * Save session state from c8ctl runtime object to disk
169
- * Always persists all session properties (activeProfile, activeTenant, outputMode)
170
418
  */
171
419
  export function saveSessionState(state) {
172
420
  const stateToSave = {
@@ -174,18 +422,16 @@ export function saveSessionState(state) {
174
422
  activeTenant: state?.activeTenant ?? c8ctl.activeTenant,
175
423
  outputMode: state?.outputMode ?? c8ctl.outputMode,
176
424
  };
177
- // Update c8ctl runtime if state is provided
178
425
  if (state) {
179
426
  c8ctl.activeProfile = state.activeProfile;
180
427
  c8ctl.activeTenant = state.activeTenant;
181
428
  c8ctl.outputMode = state.outputMode;
182
429
  }
183
430
  const path = getSessionStatePath();
184
- // Use custom replacer to preserve undefined as null in JSON
185
- writeFileSync(path, JSON.stringify(stateToSave, (key, value) => value === undefined ? null : value, 2), 'utf-8');
431
+ writeFileSync(path, JSON.stringify(stateToSave, (_, value) => (value === undefined ? null : value), 2), 'utf-8');
186
432
  }
187
433
  /**
188
- * Set active profile in session and persist to disk
434
+ * Set active profile/connection in session and persist to disk
189
435
  */
190
436
  export function setActiveProfile(name) {
191
437
  c8ctl.activeProfile = name;
@@ -205,39 +451,26 @@ export function setOutputMode(mode) {
205
451
  c8ctl.outputMode = mode;
206
452
  saveSessionState();
207
453
  }
454
+ // ============================================================================
455
+ // Cluster Configuration Resolution
456
+ // ============================================================================
208
457
  /**
209
458
  * Resolve cluster configuration from session, flags, env vars, or defaults
210
459
  * Priority: profileFlag → session profile → env vars → localhost fallback
211
460
  */
212
461
  export function resolveClusterConfig(profileFlag) {
213
- // 1. Try profile flag
462
+ // 1. Try profile flag (profile name, including modeler: prefix)
214
463
  if (profileFlag) {
215
- const profile = getProfile(profileFlag);
464
+ const profile = getProfileOrModeler(profileFlag);
216
465
  if (profile) {
217
- return {
218
- baseUrl: profile.baseUrl,
219
- clientId: profile.clientId,
220
- clientSecret: profile.clientSecret,
221
- audience: profile.audience,
222
- oAuthUrl: profile.oAuthUrl,
223
- username: profile.username,
224
- password: profile.password,
225
- };
466
+ return profileToClusterConfig(profile);
226
467
  }
227
468
  }
228
469
  // 2. Try session profile
229
470
  if (c8ctl.activeProfile) {
230
- const profile = getProfile(c8ctl.activeProfile);
471
+ const profile = getProfileOrModeler(c8ctl.activeProfile);
231
472
  if (profile) {
232
- return {
233
- baseUrl: profile.baseUrl,
234
- clientId: profile.clientId,
235
- clientSecret: profile.clientSecret,
236
- audience: profile.audience,
237
- oAuthUrl: profile.oAuthUrl,
238
- username: profile.username,
239
- password: profile.password,
240
- };
473
+ return profileToClusterConfig(profile);
241
474
  }
242
475
  }
243
476
  // 3. Try environment variables
@@ -260,8 +493,6 @@ export function resolveClusterConfig(profileFlag) {
260
493
  };
261
494
  }
262
495
  // 4. Localhost fallback with basic auth (demo/demo)
263
- // These default credentials match the docker-compose configuration
264
- // and are intended for local development only
265
496
  return {
266
497
  baseUrl: 'http://localhost:8080/v2',
267
498
  username: 'demo',
@@ -270,7 +501,7 @@ export function resolveClusterConfig(profileFlag) {
270
501
  }
271
502
  /**
272
503
  * Resolve tenant ID from session, profile, env vars, or default
273
- * Priority: session tenant → profile default tenant → env var → '<default>'
504
+ * Priority: session tenant → profile tenant → env var → '<default>'
274
505
  */
275
506
  export function resolveTenantId(profileFlag) {
276
507
  // 1. Try session tenant
@@ -280,7 +511,7 @@ export function resolveTenantId(profileFlag) {
280
511
  // 2. Try profile default tenant (from flag or session)
281
512
  const profileName = profileFlag || c8ctl.activeProfile;
282
513
  if (profileName) {
283
- const profile = getProfile(profileName);
514
+ const profile = getProfileOrModeler(profileName);
284
515
  if (profile?.defaultTenantId) {
285
516
  return profile.defaultTenantId;
286
517
  }
@@ -293,93 +524,4 @@ export function resolveTenantId(profileFlag) {
293
524
  // 4. Default tenant
294
525
  return '<default>';
295
526
  }
296
- /**
297
- * Load Camunda Modeler profiles from profiles.json
298
- * Always reads fresh from disk (no caching)
299
- *
300
- * TODO: Consider introducing caching mechanism for better performance.
301
- * Current implementation reads from disk on every call. For commands that
302
- * list profiles or look up multiple profiles, this could be optimized by
303
- * implementing per-execution memoization or a time-based cache.
304
- */
305
- export function loadModelerProfiles() {
306
- try {
307
- const modelerDir = getModelerDataDir();
308
- const profilesPath = join(modelerDir, 'profiles.json');
309
- if (!existsSync(profilesPath)) {
310
- return [];
311
- }
312
- const data = readFileSync(profilesPath, 'utf-8');
313
- const parsed = JSON.parse(data);
314
- return parsed.profiles || [];
315
- }
316
- catch (error) {
317
- // Silently return empty array if file can't be read or parsed
318
- return [];
319
- }
320
- }
321
- /**
322
- * Get a modeler profile by name or cluster ID
323
- * Accepts 'modeler:name' or 'modeler:id' format, or just 'name'/'id'
324
- */
325
- export function getModelerProfile(identifier) {
326
- const profiles = loadModelerProfiles();
327
- // Remove 'modeler:' prefix if present
328
- const searchId = identifier.startsWith('modeler:')
329
- ? identifier.substring(8)
330
- : identifier;
331
- // Search by name first, then by clusterId
332
- return profiles.find(p => p.name === searchId || p.clusterId === searchId);
333
- }
334
- /**
335
- * Construct REST API URL from modeler profile
336
- * For cloud: uses clusterUrl as-is (Camunda cloud URLs don't need /v2)
337
- * For self-managed: localhost URLs get /v2 appended
338
- * Does not derive values - uses what's provided
339
- *
340
- * Note: Self-managed clusters should include /v2 in their clusterUrl if needed
341
- */
342
- export function constructApiUrl(profile) {
343
- // If clusterUrl is provided, use it as the base
344
- if (profile.clusterUrl) {
345
- const url = profile.clusterUrl;
346
- // If it already has /v2 endpoint, use as-is
347
- if (url.includes('/v2')) {
348
- return url;
349
- }
350
- // Only append /v2 for localhost URLs
351
- // Self-managed clusters should include /v2 in their clusterUrl if needed
352
- if (url.includes('localhost') || url.includes('127.0.0.1')) {
353
- return `${url.replace(/\/$/, '')}/v2`;
354
- }
355
- // For all other URLs (including cloud), use as-is
356
- return url;
357
- }
358
- // If no clusterUrl but have clusterId, construct cloud URL
359
- if (profile.clusterId) {
360
- // Cloud cluster URLs follow pattern: https://{clusterId}.{region}.zeebe.camunda.io
361
- // We can't derive the region, so just use the clusterId as a fallback base
362
- return `https://${profile.clusterId}.zeebe.camunda.io`;
363
- }
364
- // Fallback to localhost
365
- return 'http://localhost:8080/v2';
366
- }
367
- /**
368
- * Convert a modeler profile to a c8ctl Profile
369
- */
370
- export function convertModelerProfile(modelerProfile) {
371
- const name = modelerProfile.name || modelerProfile.clusterId || 'unknown';
372
- const baseUrl = constructApiUrl(modelerProfile);
373
- return {
374
- name: `modeler:${name}`,
375
- baseUrl,
376
- clientId: modelerProfile.clientId,
377
- clientSecret: modelerProfile.clientSecret,
378
- audience: modelerProfile.audience,
379
- // Cloud clusters typically use the standard OAuth URL
380
- oAuthUrl: modelerProfile.audience ?
381
- 'https://login.cloud.camunda.io/oauth/token' :
382
- undefined,
383
- };
384
- }
385
527
  //# sourceMappingURL=config.js.map