@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/README.md +72 -5
- package/dist/commands/completion.d.ts +8 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +596 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +12 -4
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/process-definitions.d.ts +17 -0
- package/dist/commands/process-definitions.d.ts.map +1 -0
- package/dist/commands/process-definitions.js +61 -0
- package/dist/commands/process-definitions.js.map +1 -0
- package/dist/commands/profiles.d.ts +17 -8
- package/dist/commands/profiles.d.ts.map +1 -1
- package/dist/commands/profiles.js +74 -35
- package/dist/commands/profiles.js.map +1 -1
- package/dist/commands/session.js +3 -3
- package/dist/commands/session.js.map +1 -1
- package/dist/config.d.ts +104 -49
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +306 -164
- package/dist/config.js.map +1 -1
- package/dist/index.js +30 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -1,16 +1,108 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Configuration and session state management for c8ctl
|
|
3
|
-
*
|
|
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:
|
|
25
|
-
return join(process.env.
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
157
|
+
* Load c8ctl profiles from profiles.json
|
|
67
158
|
*/
|
|
68
159
|
export function loadProfiles() {
|
|
69
|
-
const
|
|
70
|
-
if (!existsSync(
|
|
160
|
+
const profilesPath = getProfilesPath();
|
|
161
|
+
if (!existsSync(profilesPath)) {
|
|
71
162
|
return [];
|
|
72
163
|
}
|
|
73
164
|
try {
|
|
74
|
-
const data = readFileSync(
|
|
75
|
-
|
|
165
|
+
const data = readFileSync(profilesPath, 'utf-8');
|
|
166
|
+
const profilesFile = JSON.parse(data);
|
|
167
|
+
return profilesFile.profiles || [];
|
|
76
168
|
}
|
|
77
|
-
catch
|
|
169
|
+
catch {
|
|
78
170
|
return [];
|
|
79
171
|
}
|
|
80
172
|
}
|
|
81
173
|
/**
|
|
82
|
-
* Save profiles to
|
|
174
|
+
* Save c8ctl profiles to profiles.json
|
|
83
175
|
*/
|
|
84
176
|
export function saveProfiles(profiles) {
|
|
85
|
-
const
|
|
86
|
-
|
|
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
|
|
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;
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|