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