@camunda8/cli 2.0.0-alpha.2 → 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 +97 -22
- 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/deployments.d.ts.map +1 -1
- package/dist/commands/deployments.js +2 -1
- package/dist/commands/deployments.js.map +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +23 -5
- 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.d.ts.map +1 -1
- package/dist/commands/session.js +7 -7
- package/dist/commands/session.js.map +1 -1
- package/dist/config.d.ts +109 -52
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +350 -179
- package/dist/config.js.map +1 -1
- package/dist/index.js +35 -6
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +1 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +20 -14
- package/dist/logger.js.map +1 -1
- package/dist/plugin-loader.d.ts +9 -0
- package/dist/plugin-loader.d.ts.map +1 -1
- package/dist/plugin-loader.js +82 -23
- package/dist/plugin-loader.js.map +1 -1
- package/dist/runtime.d.ts +18 -5
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +31 -6
- package/dist/runtime.js.map +1 -1
- package/package.json +12 -12
package/dist/config.js
CHANGED
|
@@ -1,15 +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';
|
|
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
|
+
// ============================================================================
|
|
8
102
|
/**
|
|
9
|
-
* Get platform-specific user data directory
|
|
103
|
+
* Get platform-specific user data directory for c8ctl
|
|
10
104
|
*/
|
|
11
105
|
export function getUserDataDir() {
|
|
12
|
-
// Allow override for testing
|
|
13
106
|
if (process.env.C8CTL_DATA_DIR) {
|
|
14
107
|
return process.env.C8CTL_DATA_DIR;
|
|
15
108
|
}
|
|
@@ -20,14 +113,19 @@ export function getUserDataDir() {
|
|
|
20
113
|
return join(process.env.APPDATA || join(home, 'AppData', 'Roaming'), 'c8ctl');
|
|
21
114
|
case 'darwin':
|
|
22
115
|
return join(home, 'Library', 'Application Support', 'c8ctl');
|
|
23
|
-
default:
|
|
24
|
-
return join(process.env.
|
|
116
|
+
default:
|
|
117
|
+
return join(process.env.XDG_CONFIG_HOME || join(home, '.config'), 'c8ctl');
|
|
25
118
|
}
|
|
26
119
|
}
|
|
27
120
|
/**
|
|
28
121
|
* Get platform-specific Camunda Modeler data directory
|
|
122
|
+
* Modeler stores connections in settings.json
|
|
29
123
|
*/
|
|
30
124
|
export function getModelerDataDir() {
|
|
125
|
+
// Allow override for testing
|
|
126
|
+
if (process.env.C8CTL_MODELER_DIR) {
|
|
127
|
+
return process.env.C8CTL_MODELER_DIR;
|
|
128
|
+
}
|
|
31
129
|
const plat = platform();
|
|
32
130
|
const home = homedir();
|
|
33
131
|
switch (plat) {
|
|
@@ -35,13 +133,10 @@ export function getModelerDataDir() {
|
|
|
35
133
|
return join(process.env.APPDATA || join(home, 'AppData', 'Roaming'), 'camunda-modeler');
|
|
36
134
|
case 'darwin':
|
|
37
135
|
return join(home, 'Library', 'Application Support', 'camunda-modeler');
|
|
38
|
-
default:
|
|
136
|
+
default:
|
|
39
137
|
return join(process.env.XDG_CONFIG_HOME || join(home, '.config'), 'camunda-modeler');
|
|
40
138
|
}
|
|
41
139
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Ensure user data directory exists
|
|
44
|
-
*/
|
|
45
140
|
function ensureUserDataDir() {
|
|
46
141
|
const dir = getUserDataDir();
|
|
47
142
|
if (!existsSync(dir)) {
|
|
@@ -49,63 +144,53 @@ function ensureUserDataDir() {
|
|
|
49
144
|
}
|
|
50
145
|
return dir;
|
|
51
146
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
147
|
+
function getSessionStatePath() {
|
|
148
|
+
return join(ensureUserDataDir(), 'session.json');
|
|
149
|
+
}
|
|
55
150
|
function getProfilesPath() {
|
|
56
151
|
return join(ensureUserDataDir(), 'profiles.json');
|
|
57
152
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*/
|
|
61
|
-
function getSessionStatePath() {
|
|
62
|
-
return join(ensureUserDataDir(), 'session.json');
|
|
153
|
+
function getModelerSettingsPath() {
|
|
154
|
+
return join(getModelerDataDir(), 'settings.json');
|
|
63
155
|
}
|
|
64
156
|
/**
|
|
65
|
-
* Load
|
|
157
|
+
* Load c8ctl profiles from profiles.json
|
|
66
158
|
*/
|
|
67
159
|
export function loadProfiles() {
|
|
68
|
-
const
|
|
69
|
-
if (!existsSync(
|
|
160
|
+
const profilesPath = getProfilesPath();
|
|
161
|
+
if (!existsSync(profilesPath)) {
|
|
70
162
|
return [];
|
|
71
163
|
}
|
|
72
164
|
try {
|
|
73
|
-
const data = readFileSync(
|
|
74
|
-
|
|
165
|
+
const data = readFileSync(profilesPath, 'utf-8');
|
|
166
|
+
const profilesFile = JSON.parse(data);
|
|
167
|
+
return profilesFile.profiles || [];
|
|
75
168
|
}
|
|
76
|
-
catch
|
|
169
|
+
catch {
|
|
77
170
|
return [];
|
|
78
171
|
}
|
|
79
172
|
}
|
|
80
173
|
/**
|
|
81
|
-
* Save profiles to
|
|
174
|
+
* Save c8ctl profiles to profiles.json
|
|
82
175
|
*/
|
|
83
176
|
export function saveProfiles(profiles) {
|
|
84
|
-
const
|
|
85
|
-
|
|
177
|
+
const profilesPath = getProfilesPath();
|
|
178
|
+
const profilesFile = { profiles };
|
|
179
|
+
writeFileSync(profilesPath, JSON.stringify(profilesFile, null, 2), 'utf-8');
|
|
86
180
|
}
|
|
87
181
|
/**
|
|
88
|
-
* Get a profile by name
|
|
89
|
-
* Supports both c8ctl profiles and modeler profiles (with 'modeler:' prefix)
|
|
182
|
+
* Get a c8ctl profile by name
|
|
90
183
|
*/
|
|
91
184
|
export function getProfile(name) {
|
|
92
|
-
// Check if this is a modeler profile request
|
|
93
|
-
if (name.startsWith('modeler:')) {
|
|
94
|
-
const modelerProfile = getModelerProfile(name);
|
|
95
|
-
if (modelerProfile) {
|
|
96
|
-
return convertModelerProfile(modelerProfile);
|
|
97
|
-
}
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
// Check c8ctl profiles
|
|
101
185
|
const profiles = loadProfiles();
|
|
102
186
|
return profiles.find(p => p.name === name);
|
|
103
187
|
}
|
|
104
188
|
/**
|
|
105
|
-
* Add
|
|
189
|
+
* Add a c8ctl profile
|
|
106
190
|
*/
|
|
107
191
|
export function addProfile(profile) {
|
|
108
192
|
const profiles = loadProfiles();
|
|
193
|
+
// Check if profile already exists
|
|
109
194
|
const existingIndex = profiles.findIndex(p => p.name === profile.name);
|
|
110
195
|
if (existingIndex >= 0) {
|
|
111
196
|
profiles[existingIndex] = profile;
|
|
@@ -116,98 +201,276 @@ export function addProfile(profile) {
|
|
|
116
201
|
saveProfiles(profiles);
|
|
117
202
|
}
|
|
118
203
|
/**
|
|
119
|
-
* Remove a profile
|
|
204
|
+
* Remove a c8ctl profile by name
|
|
120
205
|
*/
|
|
121
206
|
export function removeProfile(name) {
|
|
122
207
|
const profiles = loadProfiles();
|
|
123
208
|
const filtered = profiles.filter(p => p.name !== name);
|
|
124
209
|
if (filtered.length === profiles.length) {
|
|
125
|
-
return false;
|
|
210
|
+
return false;
|
|
126
211
|
}
|
|
127
212
|
saveProfiles(filtered);
|
|
128
213
|
return true;
|
|
129
214
|
}
|
|
215
|
+
// ============================================================================
|
|
216
|
+
// Modeler Connection Management - READ-ONLY from settings.json
|
|
217
|
+
// ============================================================================
|
|
218
|
+
export const MODELER_PREFIX = 'modeler:';
|
|
130
219
|
/**
|
|
131
|
-
* Load
|
|
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
|
+
// ============================================================================
|
|
384
|
+
/**
|
|
385
|
+
* Load session state from disk and populate c8ctl runtime object
|
|
132
386
|
*/
|
|
133
387
|
export function loadSessionState() {
|
|
134
388
|
const path = getSessionStatePath();
|
|
135
389
|
if (!existsSync(path)) {
|
|
136
|
-
return {
|
|
390
|
+
return {
|
|
391
|
+
activeProfile: c8ctl.activeProfile,
|
|
392
|
+
activeTenant: c8ctl.activeTenant,
|
|
393
|
+
outputMode: c8ctl.outputMode,
|
|
394
|
+
};
|
|
137
395
|
}
|
|
138
396
|
try {
|
|
139
397
|
const data = readFileSync(path, 'utf-8');
|
|
140
|
-
|
|
398
|
+
const state = JSON.parse(data);
|
|
399
|
+
c8ctl.activeProfile = state.activeProfile === null ? undefined : state.activeProfile;
|
|
400
|
+
c8ctl.activeTenant = state.activeTenant === null ? undefined : state.activeTenant;
|
|
401
|
+
c8ctl.outputMode = state.outputMode || 'text';
|
|
402
|
+
return {
|
|
403
|
+
activeProfile: c8ctl.activeProfile,
|
|
404
|
+
activeTenant: c8ctl.activeTenant,
|
|
405
|
+
outputMode: c8ctl.outputMode,
|
|
406
|
+
};
|
|
141
407
|
}
|
|
142
|
-
catch
|
|
143
|
-
return {
|
|
408
|
+
catch {
|
|
409
|
+
return {
|
|
410
|
+
activeProfile: c8ctl.activeProfile,
|
|
411
|
+
activeTenant: c8ctl.activeTenant,
|
|
412
|
+
outputMode: c8ctl.outputMode,
|
|
413
|
+
};
|
|
144
414
|
}
|
|
145
415
|
}
|
|
146
416
|
/**
|
|
147
|
-
* Save session state to disk
|
|
417
|
+
* Save session state from c8ctl runtime object to disk
|
|
148
418
|
*/
|
|
149
419
|
export function saveSessionState(state) {
|
|
420
|
+
const stateToSave = {
|
|
421
|
+
activeProfile: state?.activeProfile ?? c8ctl.activeProfile,
|
|
422
|
+
activeTenant: state?.activeTenant ?? c8ctl.activeTenant,
|
|
423
|
+
outputMode: state?.outputMode ?? c8ctl.outputMode,
|
|
424
|
+
};
|
|
425
|
+
if (state) {
|
|
426
|
+
c8ctl.activeProfile = state.activeProfile;
|
|
427
|
+
c8ctl.activeTenant = state.activeTenant;
|
|
428
|
+
c8ctl.outputMode = state.outputMode;
|
|
429
|
+
}
|
|
150
430
|
const path = getSessionStatePath();
|
|
151
|
-
writeFileSync(path, JSON.stringify(
|
|
431
|
+
writeFileSync(path, JSON.stringify(stateToSave, (_, value) => (value === undefined ? null : value), 2), 'utf-8');
|
|
152
432
|
}
|
|
153
433
|
/**
|
|
154
|
-
* Set active profile in session
|
|
434
|
+
* Set active profile/connection in session and persist to disk
|
|
155
435
|
*/
|
|
156
436
|
export function setActiveProfile(name) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
saveSessionState(state);
|
|
437
|
+
c8ctl.activeProfile = name;
|
|
438
|
+
saveSessionState();
|
|
160
439
|
}
|
|
161
440
|
/**
|
|
162
|
-
* Set active tenant in session
|
|
441
|
+
* Set active tenant in session and persist to disk
|
|
163
442
|
*/
|
|
164
443
|
export function setActiveTenant(tenantId) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
saveSessionState(state);
|
|
444
|
+
c8ctl.activeTenant = tenantId;
|
|
445
|
+
saveSessionState();
|
|
168
446
|
}
|
|
169
447
|
/**
|
|
170
|
-
* Set output mode in session
|
|
448
|
+
* Set output mode in session and persist to disk
|
|
171
449
|
*/
|
|
172
450
|
export function setOutputMode(mode) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
saveSessionState(state);
|
|
451
|
+
c8ctl.outputMode = mode;
|
|
452
|
+
saveSessionState();
|
|
176
453
|
}
|
|
454
|
+
// ============================================================================
|
|
455
|
+
// Cluster Configuration Resolution
|
|
456
|
+
// ============================================================================
|
|
177
457
|
/**
|
|
178
458
|
* Resolve cluster configuration from session, flags, env vars, or defaults
|
|
179
459
|
* Priority: profileFlag → session profile → env vars → localhost fallback
|
|
180
460
|
*/
|
|
181
461
|
export function resolveClusterConfig(profileFlag) {
|
|
182
|
-
// 1. Try profile flag
|
|
462
|
+
// 1. Try profile flag (profile name, including modeler: prefix)
|
|
183
463
|
if (profileFlag) {
|
|
184
|
-
const profile =
|
|
464
|
+
const profile = getProfileOrModeler(profileFlag);
|
|
185
465
|
if (profile) {
|
|
186
|
-
return
|
|
187
|
-
baseUrl: profile.baseUrl,
|
|
188
|
-
clientId: profile.clientId,
|
|
189
|
-
clientSecret: profile.clientSecret,
|
|
190
|
-
audience: profile.audience,
|
|
191
|
-
oAuthUrl: profile.oAuthUrl,
|
|
192
|
-
username: profile.username,
|
|
193
|
-
password: profile.password,
|
|
194
|
-
};
|
|
466
|
+
return profileToClusterConfig(profile);
|
|
195
467
|
}
|
|
196
468
|
}
|
|
197
469
|
// 2. Try session profile
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const profile = getProfile(session.activeProfile);
|
|
470
|
+
if (c8ctl.activeProfile) {
|
|
471
|
+
const profile = getProfileOrModeler(c8ctl.activeProfile);
|
|
201
472
|
if (profile) {
|
|
202
|
-
return
|
|
203
|
-
baseUrl: profile.baseUrl,
|
|
204
|
-
clientId: profile.clientId,
|
|
205
|
-
clientSecret: profile.clientSecret,
|
|
206
|
-
audience: profile.audience,
|
|
207
|
-
oAuthUrl: profile.oAuthUrl,
|
|
208
|
-
username: profile.username,
|
|
209
|
-
password: profile.password,
|
|
210
|
-
};
|
|
473
|
+
return profileToClusterConfig(profile);
|
|
211
474
|
}
|
|
212
475
|
}
|
|
213
476
|
// 3. Try environment variables
|
|
@@ -230,8 +493,6 @@ export function resolveClusterConfig(profileFlag) {
|
|
|
230
493
|
};
|
|
231
494
|
}
|
|
232
495
|
// 4. Localhost fallback with basic auth (demo/demo)
|
|
233
|
-
// These default credentials match the docker-compose configuration
|
|
234
|
-
// and are intended for local development only
|
|
235
496
|
return {
|
|
236
497
|
baseUrl: 'http://localhost:8080/v2',
|
|
237
498
|
username: 'demo',
|
|
@@ -240,18 +501,17 @@ export function resolveClusterConfig(profileFlag) {
|
|
|
240
501
|
}
|
|
241
502
|
/**
|
|
242
503
|
* Resolve tenant ID from session, profile, env vars, or default
|
|
243
|
-
* Priority: session tenant → profile
|
|
504
|
+
* Priority: session tenant → profile tenant → env var → '<default>'
|
|
244
505
|
*/
|
|
245
506
|
export function resolveTenantId(profileFlag) {
|
|
246
507
|
// 1. Try session tenant
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return session.activeTenant;
|
|
508
|
+
if (c8ctl.activeTenant) {
|
|
509
|
+
return c8ctl.activeTenant;
|
|
250
510
|
}
|
|
251
511
|
// 2. Try profile default tenant (from flag or session)
|
|
252
|
-
const profileName = profileFlag ||
|
|
512
|
+
const profileName = profileFlag || c8ctl.activeProfile;
|
|
253
513
|
if (profileName) {
|
|
254
|
-
const profile =
|
|
514
|
+
const profile = getProfileOrModeler(profileName);
|
|
255
515
|
if (profile?.defaultTenantId) {
|
|
256
516
|
return profile.defaultTenantId;
|
|
257
517
|
}
|
|
@@ -264,93 +524,4 @@ export function resolveTenantId(profileFlag) {
|
|
|
264
524
|
// 4. Default tenant
|
|
265
525
|
return '<default>';
|
|
266
526
|
}
|
|
267
|
-
/**
|
|
268
|
-
* Load Camunda Modeler profiles from profiles.json
|
|
269
|
-
* Always reads fresh from disk (no caching)
|
|
270
|
-
*
|
|
271
|
-
* TODO: Consider introducing caching mechanism for better performance.
|
|
272
|
-
* Current implementation reads from disk on every call. For commands that
|
|
273
|
-
* list profiles or look up multiple profiles, this could be optimized by
|
|
274
|
-
* implementing per-execution memoization or a time-based cache.
|
|
275
|
-
*/
|
|
276
|
-
export function loadModelerProfiles() {
|
|
277
|
-
try {
|
|
278
|
-
const modelerDir = getModelerDataDir();
|
|
279
|
-
const profilesPath = join(modelerDir, 'profiles.json');
|
|
280
|
-
if (!existsSync(profilesPath)) {
|
|
281
|
-
return [];
|
|
282
|
-
}
|
|
283
|
-
const data = readFileSync(profilesPath, 'utf-8');
|
|
284
|
-
const parsed = JSON.parse(data);
|
|
285
|
-
return parsed.profiles || [];
|
|
286
|
-
}
|
|
287
|
-
catch (error) {
|
|
288
|
-
// Silently return empty array if file can't be read or parsed
|
|
289
|
-
return [];
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Get a modeler profile by name or cluster ID
|
|
294
|
-
* Accepts 'modeler:name' or 'modeler:id' format, or just 'name'/'id'
|
|
295
|
-
*/
|
|
296
|
-
export function getModelerProfile(identifier) {
|
|
297
|
-
const profiles = loadModelerProfiles();
|
|
298
|
-
// Remove 'modeler:' prefix if present
|
|
299
|
-
const searchId = identifier.startsWith('modeler:')
|
|
300
|
-
? identifier.substring(8)
|
|
301
|
-
: identifier;
|
|
302
|
-
// Search by name first, then by clusterId
|
|
303
|
-
return profiles.find(p => p.name === searchId || p.clusterId === searchId);
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Construct REST API URL from modeler profile
|
|
307
|
-
* For cloud: uses clusterUrl as-is (Camunda cloud URLs don't need /v2)
|
|
308
|
-
* For self-managed: localhost URLs get /v2 appended
|
|
309
|
-
* Does not derive values - uses what's provided
|
|
310
|
-
*
|
|
311
|
-
* Note: Self-managed clusters should include /v2 in their clusterUrl if needed
|
|
312
|
-
*/
|
|
313
|
-
export function constructApiUrl(profile) {
|
|
314
|
-
// If clusterUrl is provided, use it as the base
|
|
315
|
-
if (profile.clusterUrl) {
|
|
316
|
-
const url = profile.clusterUrl;
|
|
317
|
-
// If it already has /v2 endpoint, use as-is
|
|
318
|
-
if (url.includes('/v2')) {
|
|
319
|
-
return url;
|
|
320
|
-
}
|
|
321
|
-
// Only append /v2 for localhost URLs
|
|
322
|
-
// Self-managed clusters should include /v2 in their clusterUrl if needed
|
|
323
|
-
if (url.includes('localhost') || url.includes('127.0.0.1')) {
|
|
324
|
-
return `${url.replace(/\/$/, '')}/v2`;
|
|
325
|
-
}
|
|
326
|
-
// For all other URLs (including cloud), use as-is
|
|
327
|
-
return url;
|
|
328
|
-
}
|
|
329
|
-
// If no clusterUrl but have clusterId, construct cloud URL
|
|
330
|
-
if (profile.clusterId) {
|
|
331
|
-
// Cloud cluster URLs follow pattern: https://{clusterId}.{region}.zeebe.camunda.io
|
|
332
|
-
// We can't derive the region, so just use the clusterId as a fallback base
|
|
333
|
-
return `https://${profile.clusterId}.zeebe.camunda.io`;
|
|
334
|
-
}
|
|
335
|
-
// Fallback to localhost
|
|
336
|
-
return 'http://localhost:8080/v2';
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Convert a modeler profile to a c8ctl Profile
|
|
340
|
-
*/
|
|
341
|
-
export function convertModelerProfile(modelerProfile) {
|
|
342
|
-
const name = modelerProfile.name || modelerProfile.clusterId || 'unknown';
|
|
343
|
-
const baseUrl = constructApiUrl(modelerProfile);
|
|
344
|
-
return {
|
|
345
|
-
name: `modeler:${name}`,
|
|
346
|
-
baseUrl,
|
|
347
|
-
clientId: modelerProfile.clientId,
|
|
348
|
-
clientSecret: modelerProfile.clientSecret,
|
|
349
|
-
audience: modelerProfile.audience,
|
|
350
|
-
// Cloud clusters typically use the standard OAuth URL
|
|
351
|
-
oAuthUrl: modelerProfile.audience ?
|
|
352
|
-
'https://login.cloud.camunda.io/oauth/token' :
|
|
353
|
-
undefined,
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
527
|
//# sourceMappingURL=config.js.map
|