@bestend/confluence-cli 1.16.0 → 2.0.0
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/.claude/skills/confluence/SKILL.md +722 -0
- package/README.md +251 -21
- package/bin/confluence.js +836 -116
- package/bin/index.js +6 -1
- package/lib/config.js +242 -40
- package/lib/confluence-client.js +309 -52
- package/package.json +10 -4
- package/.eslintrc.js +0 -23
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -34
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -26
- package/.github/ISSUE_TEMPLATE/feedback.md +0 -37
- package/.github/pull_request_template.md +0 -31
- package/.github/workflows/ci.yml +0 -28
- package/.github/workflows/publish.yml +0 -38
- package/.releaserc +0 -17
- package/AGENTS.md +0 -105
- package/CHANGELOG.md +0 -232
- package/CONTRIBUTING.md +0 -246
- package/docs/PROMOTION.md +0 -63
- package/eslint.config.js +0 -33
- package/examples/copy-tree-example.sh +0 -117
- package/examples/create-child-page-example.sh +0 -67
- package/examples/demo-page-management.sh +0 -68
- package/examples/demo.sh +0 -43
- package/examples/sample-page.md +0 -30
- package/jest.config.js +0 -13
- package/llms.txt +0 -46
- package/tests/confluence-client.test.js +0 -458
package/bin/index.js
CHANGED
|
@@ -21,4 +21,9 @@ if (!nodeVersion.startsWith('v') ||
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// Load the main CLI application
|
|
24
|
-
require('./confluence.js');
|
|
24
|
+
const { program } = require('./confluence.js');
|
|
25
|
+
|
|
26
|
+
if (process.argv.length <= 2) {
|
|
27
|
+
program.help({ error: false });
|
|
28
|
+
}
|
|
29
|
+
program.parse(process.argv);
|
package/lib/config.js
CHANGED
|
@@ -6,12 +6,15 @@ const chalk = require('chalk');
|
|
|
6
6
|
|
|
7
7
|
const CONFIG_DIR = path.join(os.homedir(), '.confluence-cli');
|
|
8
8
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
9
|
+
const DEFAULT_PROFILE = 'default';
|
|
9
10
|
|
|
10
11
|
const AUTH_CHOICES = [
|
|
11
|
-
{ name: 'Basic (
|
|
12
|
+
{ name: 'Basic (credentials)', value: 'basic' },
|
|
12
13
|
{ name: 'Bearer token', value: 'bearer' }
|
|
13
14
|
];
|
|
14
15
|
|
|
16
|
+
const isValidProfileName = (name) => /^[a-zA-Z0-9_-]+$/.test(name);
|
|
17
|
+
|
|
15
18
|
const requiredInput = (label) => (input) => {
|
|
16
19
|
if (!input || !input.trim()) {
|
|
17
20
|
return `${label} is required`;
|
|
@@ -19,6 +22,19 @@ const requiredInput = (label) => (input) => {
|
|
|
19
22
|
return true;
|
|
20
23
|
};
|
|
21
24
|
|
|
25
|
+
const PROTOCOL_CHOICES = [
|
|
26
|
+
{ name: 'HTTPS (recommended)', value: 'https' },
|
|
27
|
+
{ name: 'HTTP', value: 'http' }
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const normalizeProtocol = (rawValue) => {
|
|
31
|
+
const normalized = (rawValue || '').trim().toLowerCase();
|
|
32
|
+
if (normalized === 'http' || normalized === 'https') {
|
|
33
|
+
return normalized;
|
|
34
|
+
}
|
|
35
|
+
return 'https';
|
|
36
|
+
};
|
|
37
|
+
|
|
22
38
|
const normalizeAuthType = (rawValue, hasEmail) => {
|
|
23
39
|
const normalized = (rawValue || '').trim().toLowerCase();
|
|
24
40
|
if (normalized === 'basic' || normalized === 'bearer') {
|
|
@@ -55,6 +71,50 @@ const normalizeApiPath = (rawValue, domain) => {
|
|
|
55
71
|
return withoutTrailing || inferApiPath(domain);
|
|
56
72
|
};
|
|
57
73
|
|
|
74
|
+
// Read config file with backward compatibility for old flat format
|
|
75
|
+
function readConfigFile() {
|
|
76
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const raw = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
82
|
+
|
|
83
|
+
// Detect old flat format (has domain at top level, no profiles key)
|
|
84
|
+
if (raw.domain && !raw.profiles) {
|
|
85
|
+
const profile = {
|
|
86
|
+
domain: raw.domain,
|
|
87
|
+
protocol: raw.protocol,
|
|
88
|
+
apiPath: raw.apiPath,
|
|
89
|
+
token: raw.token,
|
|
90
|
+
authType: raw.authType
|
|
91
|
+
};
|
|
92
|
+
if (raw.email) {
|
|
93
|
+
profile.email = raw.email;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
activeProfile: DEFAULT_PROFILE,
|
|
97
|
+
profiles: { [DEFAULT_PROFILE]: profile }
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return raw;
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Write the full multi-profile config structure
|
|
108
|
+
function saveConfigFile(data) {
|
|
109
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
110
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
111
|
+
} else {
|
|
112
|
+
fs.chmodSync(CONFIG_DIR, 0o700);
|
|
113
|
+
}
|
|
114
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
115
|
+
fs.chmodSync(CONFIG_FILE, 0o600);
|
|
116
|
+
}
|
|
117
|
+
|
|
58
118
|
// Helper function to validate CLI-provided options
|
|
59
119
|
const validateCliOptions = (options) => {
|
|
60
120
|
const errors = [];
|
|
@@ -84,6 +144,10 @@ const validateCliOptions = (options) => {
|
|
|
84
144
|
}
|
|
85
145
|
}
|
|
86
146
|
|
|
147
|
+
if (options.protocol && !['http', 'https'].includes(options.protocol.toLowerCase())) {
|
|
148
|
+
errors.push('--protocol must be "http" or "https"');
|
|
149
|
+
}
|
|
150
|
+
|
|
87
151
|
if (options.authType && !['basic', 'bearer'].includes(options.authType.toLowerCase())) {
|
|
88
152
|
errors.push('--auth-type must be "basic" or "bearer"');
|
|
89
153
|
}
|
|
@@ -91,29 +155,47 @@ const validateCliOptions = (options) => {
|
|
|
91
155
|
// Check if basic auth is provided with email
|
|
92
156
|
const normAuthType = options.authType ? normalizeAuthType(options.authType, Boolean(options.email)) : null;
|
|
93
157
|
if (normAuthType === 'basic' && !options.email) {
|
|
94
|
-
errors.push('--email is required when using basic authentication');
|
|
158
|
+
errors.push('--email is required when using basic authentication (use your username for on-premise)');
|
|
95
159
|
}
|
|
96
160
|
|
|
97
161
|
return errors;
|
|
98
162
|
};
|
|
99
163
|
|
|
100
164
|
// Helper function to save configuration with validation
|
|
101
|
-
const saveConfig = (configData) => {
|
|
102
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
103
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
104
|
-
}
|
|
105
|
-
|
|
165
|
+
const saveConfig = (configData, profileName) => {
|
|
106
166
|
const config = {
|
|
107
167
|
domain: configData.domain.trim(),
|
|
168
|
+
protocol: normalizeProtocol(configData.protocol),
|
|
108
169
|
apiPath: normalizeApiPath(configData.apiPath, configData.domain),
|
|
109
170
|
token: configData.token.trim(),
|
|
110
|
-
authType: configData.authType
|
|
111
|
-
email: configData.authType === 'basic' && configData.email ? configData.email.trim() : undefined
|
|
171
|
+
authType: configData.authType
|
|
112
172
|
};
|
|
113
173
|
|
|
114
|
-
|
|
174
|
+
if (configData.authType === 'basic' && configData.email) {
|
|
175
|
+
config.email = configData.email.trim();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (configData.readOnly) {
|
|
179
|
+
config.readOnly = true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Read existing config file (or create new structure)
|
|
183
|
+
const fileData = readConfigFile() || { activeProfile: DEFAULT_PROFILE, profiles: {} };
|
|
184
|
+
|
|
185
|
+
const targetProfile = profileName || fileData.activeProfile || DEFAULT_PROFILE;
|
|
186
|
+
fileData.profiles[targetProfile] = config;
|
|
187
|
+
|
|
188
|
+
// If this is the first profile, make it active
|
|
189
|
+
if (!fileData.activeProfile || !fileData.profiles[fileData.activeProfile]) {
|
|
190
|
+
fileData.activeProfile = targetProfile;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
saveConfigFile(fileData);
|
|
115
194
|
|
|
116
195
|
console.log(chalk.green('✅ Configuration saved successfully!'));
|
|
196
|
+
if (profileName) {
|
|
197
|
+
console.log(`Profile: ${chalk.cyan(targetProfile)}`);
|
|
198
|
+
}
|
|
117
199
|
console.log(`Config file location: ${chalk.gray(CONFIG_FILE)}`);
|
|
118
200
|
console.log(chalk.yellow('\n💡 Tip: You can regenerate this config anytime by running "confluence init"'));
|
|
119
201
|
};
|
|
@@ -122,6 +204,17 @@ const saveConfig = (configData) => {
|
|
|
122
204
|
const promptForMissingValues = async (providedValues) => {
|
|
123
205
|
const questions = [];
|
|
124
206
|
|
|
207
|
+
// Protocol question
|
|
208
|
+
if (!providedValues.protocol) {
|
|
209
|
+
questions.push({
|
|
210
|
+
type: 'list',
|
|
211
|
+
name: 'protocol',
|
|
212
|
+
message: 'Protocol:',
|
|
213
|
+
choices: PROTOCOL_CHOICES,
|
|
214
|
+
default: 'https'
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
125
218
|
// Domain question
|
|
126
219
|
if (!providedValues.domain) {
|
|
127
220
|
questions.push({
|
|
@@ -175,12 +268,12 @@ const promptForMissingValues = async (providedValues) => {
|
|
|
175
268
|
questions.push({
|
|
176
269
|
type: 'input',
|
|
177
270
|
name: 'email',
|
|
178
|
-
message: '
|
|
271
|
+
message: 'Email / username:',
|
|
179
272
|
when: (responses) => {
|
|
180
273
|
const authType = providedValues.authType || responses.authType;
|
|
181
274
|
return authType === 'basic';
|
|
182
275
|
},
|
|
183
|
-
validate: requiredInput('Email')
|
|
276
|
+
validate: requiredInput('Email / username')
|
|
184
277
|
});
|
|
185
278
|
}
|
|
186
279
|
|
|
@@ -189,8 +282,8 @@ const promptForMissingValues = async (providedValues) => {
|
|
|
189
282
|
questions.push({
|
|
190
283
|
type: 'password',
|
|
191
284
|
name: 'token',
|
|
192
|
-
message: 'API
|
|
193
|
-
validate: requiredInput('API
|
|
285
|
+
message: 'API token / password:',
|
|
286
|
+
validate: requiredInput('API token / password')
|
|
194
287
|
});
|
|
195
288
|
}
|
|
196
289
|
|
|
@@ -203,8 +296,19 @@ const promptForMissingValues = async (providedValues) => {
|
|
|
203
296
|
};
|
|
204
297
|
|
|
205
298
|
async function initConfig(cliOptions = {}) {
|
|
299
|
+
const profileName = cliOptions.profile;
|
|
300
|
+
|
|
301
|
+
// Validate profile name if provided
|
|
302
|
+
if (profileName && !isValidProfileName(profileName)) {
|
|
303
|
+
console.error(chalk.red('❌ Invalid profile name. Use only letters, numbers, hyphens, and underscores.'));
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const readOnly = cliOptions.readOnly || false;
|
|
308
|
+
|
|
206
309
|
// Extract provided values from CLI options
|
|
207
310
|
const providedValues = {
|
|
311
|
+
protocol: cliOptions.protocol,
|
|
208
312
|
domain: cliOptions.domain,
|
|
209
313
|
apiPath: cliOptions.apiPath,
|
|
210
314
|
authType: cliOptions.authType,
|
|
@@ -218,9 +322,19 @@ async function initConfig(cliOptions = {}) {
|
|
|
218
322
|
if (!hasCliOptions) {
|
|
219
323
|
// Interactive mode: no CLI options provided
|
|
220
324
|
console.log(chalk.blue('🚀 Confluence CLI Configuration'));
|
|
325
|
+
if (profileName) {
|
|
326
|
+
console.log(`Profile: ${chalk.cyan(profileName)}`);
|
|
327
|
+
}
|
|
221
328
|
console.log('Please provide your Confluence connection details:\n');
|
|
222
329
|
|
|
223
330
|
const answers = await inquirer.prompt([
|
|
331
|
+
{
|
|
332
|
+
type: 'list',
|
|
333
|
+
name: 'protocol',
|
|
334
|
+
message: 'Protocol:',
|
|
335
|
+
choices: PROTOCOL_CHOICES,
|
|
336
|
+
default: 'https'
|
|
337
|
+
},
|
|
224
338
|
{
|
|
225
339
|
type: 'input',
|
|
226
340
|
name: 'domain',
|
|
@@ -258,19 +372,19 @@ async function initConfig(cliOptions = {}) {
|
|
|
258
372
|
{
|
|
259
373
|
type: 'input',
|
|
260
374
|
name: 'email',
|
|
261
|
-
message: '
|
|
375
|
+
message: 'Email / username:',
|
|
262
376
|
when: (responses) => responses.authType === 'basic',
|
|
263
|
-
validate: requiredInput('Email')
|
|
377
|
+
validate: requiredInput('Email / username')
|
|
264
378
|
},
|
|
265
379
|
{
|
|
266
380
|
type: 'password',
|
|
267
381
|
name: 'token',
|
|
268
|
-
message: 'API
|
|
269
|
-
validate: requiredInput('API
|
|
382
|
+
message: 'API token / password:',
|
|
383
|
+
validate: requiredInput('API token / password')
|
|
270
384
|
}
|
|
271
385
|
]);
|
|
272
386
|
|
|
273
|
-
saveConfig(answers);
|
|
387
|
+
saveConfig({ ...answers, readOnly }, profileName);
|
|
274
388
|
return;
|
|
275
389
|
}
|
|
276
390
|
|
|
@@ -288,8 +402,8 @@ async function initConfig(cliOptions = {}) {
|
|
|
288
402
|
// Check if all required values are provided for non-interactive mode
|
|
289
403
|
// Non-interactive requires: domain, token, and either authType or email (for inference)
|
|
290
404
|
const hasRequiredValues = Boolean(
|
|
291
|
-
providedValues.domain &&
|
|
292
|
-
providedValues.token &&
|
|
405
|
+
providedValues.domain &&
|
|
406
|
+
providedValues.token &&
|
|
293
407
|
(providedValues.authType || providedValues.email)
|
|
294
408
|
);
|
|
295
409
|
|
|
@@ -304,7 +418,7 @@ async function initConfig(cliOptions = {}) {
|
|
|
304
418
|
|
|
305
419
|
const normalizedAuthType = normalizeAuthType(inferredAuthType, Boolean(providedValues.email));
|
|
306
420
|
const normalizedDomain = providedValues.domain.trim();
|
|
307
|
-
|
|
421
|
+
|
|
308
422
|
// Verify basic auth has email
|
|
309
423
|
if (normalizedAuthType === 'basic' && !providedValues.email) {
|
|
310
424
|
console.error(chalk.red('❌ Email is required for basic authentication'));
|
|
@@ -318,13 +432,15 @@ async function initConfig(cliOptions = {}) {
|
|
|
318
432
|
|
|
319
433
|
const configData = {
|
|
320
434
|
domain: normalizedDomain,
|
|
435
|
+
protocol: normalizeProtocol(providedValues.protocol),
|
|
321
436
|
apiPath: providedValues.apiPath || inferApiPath(normalizedDomain),
|
|
322
437
|
token: providedValues.token,
|
|
323
438
|
authType: normalizedAuthType,
|
|
324
|
-
email: providedValues.email
|
|
439
|
+
email: providedValues.email,
|
|
440
|
+
readOnly
|
|
325
441
|
};
|
|
326
442
|
|
|
327
|
-
saveConfig(configData);
|
|
443
|
+
saveConfig(configData, profileName);
|
|
328
444
|
} catch (error) {
|
|
329
445
|
console.error(chalk.red(`❌ ${error.message}`));
|
|
330
446
|
process.exit(1);
|
|
@@ -335,26 +451,31 @@ async function initConfig(cliOptions = {}) {
|
|
|
335
451
|
// Hybrid mode: some values provided, prompt for the rest
|
|
336
452
|
try {
|
|
337
453
|
console.log(chalk.blue('🚀 Confluence CLI Configuration'));
|
|
454
|
+
if (profileName) {
|
|
455
|
+
console.log(`Profile: ${chalk.cyan(profileName)}`);
|
|
456
|
+
}
|
|
338
457
|
console.log('Completing configuration with interactive prompts:\n');
|
|
339
458
|
|
|
340
459
|
const mergedValues = await promptForMissingValues(providedValues);
|
|
341
|
-
|
|
460
|
+
|
|
342
461
|
// Normalize auth type
|
|
343
462
|
mergedValues.authType = normalizeAuthType(mergedValues.authType, Boolean(mergedValues.email));
|
|
344
|
-
|
|
345
|
-
saveConfig(mergedValues);
|
|
463
|
+
|
|
464
|
+
saveConfig({ ...mergedValues, readOnly }, profileName);
|
|
346
465
|
} catch (error) {
|
|
347
466
|
console.error(chalk.red(`❌ ${error.message}`));
|
|
348
467
|
process.exit(1);
|
|
349
468
|
}
|
|
350
469
|
}
|
|
351
470
|
|
|
352
|
-
function getConfig() {
|
|
471
|
+
function getConfig(profileName) {
|
|
353
472
|
const envDomain = process.env.CONFLUENCE_DOMAIN || process.env.CONFLUENCE_HOST;
|
|
354
|
-
const envToken = process.env.CONFLUENCE_API_TOKEN;
|
|
355
|
-
const envEmail = process.env.CONFLUENCE_EMAIL;
|
|
473
|
+
const envToken = process.env.CONFLUENCE_API_TOKEN || process.env.CONFLUENCE_PASSWORD;
|
|
474
|
+
const envEmail = process.env.CONFLUENCE_EMAIL || process.env.CONFLUENCE_USERNAME;
|
|
356
475
|
const envAuthType = process.env.CONFLUENCE_AUTH_TYPE;
|
|
357
476
|
const envApiPath = process.env.CONFLUENCE_API_PATH;
|
|
477
|
+
const envProtocol = process.env.CONFLUENCE_PROTOCOL;
|
|
478
|
+
const envReadOnly = process.env.CONFLUENCE_READ_ONLY;
|
|
358
479
|
|
|
359
480
|
if (envDomain && envToken) {
|
|
360
481
|
const authType = normalizeAuthType(envAuthType, Boolean(envEmail));
|
|
@@ -368,29 +489,50 @@ function getConfig() {
|
|
|
368
489
|
}
|
|
369
490
|
|
|
370
491
|
if (authType === 'basic' && !envEmail) {
|
|
371
|
-
console.error(chalk.red('❌ Basic authentication requires CONFLUENCE_EMAIL.'));
|
|
372
|
-
console.log(chalk.yellow('Set CONFLUENCE_EMAIL or switch to bearer auth by setting CONFLUENCE_AUTH_TYPE=bearer.'));
|
|
492
|
+
console.error(chalk.red('❌ Basic authentication requires CONFLUENCE_EMAIL or CONFLUENCE_USERNAME.'));
|
|
493
|
+
console.log(chalk.yellow('Set CONFLUENCE_EMAIL (or CONFLUENCE_USERNAME for on-premise) or switch to bearer auth by setting CONFLUENCE_AUTH_TYPE=bearer.'));
|
|
373
494
|
process.exit(1);
|
|
374
495
|
}
|
|
375
496
|
|
|
376
497
|
return {
|
|
377
498
|
domain: envDomain.trim(),
|
|
499
|
+
protocol: normalizeProtocol(envProtocol),
|
|
378
500
|
apiPath,
|
|
379
501
|
token: envToken.trim(),
|
|
380
502
|
email: envEmail ? envEmail.trim() : undefined,
|
|
381
|
-
authType
|
|
503
|
+
authType,
|
|
504
|
+
readOnly: envReadOnly === 'true'
|
|
382
505
|
};
|
|
383
506
|
}
|
|
384
507
|
|
|
385
|
-
|
|
508
|
+
// Resolve profile: explicit param > CONFLUENCE_PROFILE env var > activeProfile > default
|
|
509
|
+
const resolvedProfileName = profileName
|
|
510
|
+
|| process.env.CONFLUENCE_PROFILE
|
|
511
|
+
|| null;
|
|
512
|
+
|
|
513
|
+
const fileData = readConfigFile();
|
|
514
|
+
|
|
515
|
+
if (!fileData) {
|
|
386
516
|
console.error(chalk.red('❌ No configuration found!'));
|
|
387
517
|
console.log(chalk.yellow('Please run "confluence init" to set up your configuration.'));
|
|
388
|
-
console.log(chalk.gray('Or set environment variables: CONFLUENCE_DOMAIN, CONFLUENCE_API_TOKEN, CONFLUENCE_EMAIL, and optionally CONFLUENCE_API_PATH.'));
|
|
518
|
+
console.log(chalk.gray('Or set environment variables: CONFLUENCE_DOMAIN, CONFLUENCE_API_TOKEN (or CONFLUENCE_PASSWORD), CONFLUENCE_EMAIL (or CONFLUENCE_USERNAME), and optionally CONFLUENCE_API_PATH, CONFLUENCE_PROTOCOL.'));
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const targetProfile = resolvedProfileName || fileData.activeProfile || DEFAULT_PROFILE;
|
|
523
|
+
const storedConfig = fileData.profiles && fileData.profiles[targetProfile];
|
|
524
|
+
|
|
525
|
+
if (!storedConfig) {
|
|
526
|
+
console.error(chalk.red(`❌ Profile "${targetProfile}" not found!`));
|
|
527
|
+
const available = fileData.profiles ? Object.keys(fileData.profiles) : [];
|
|
528
|
+
if (available.length > 0) {
|
|
529
|
+
console.log(chalk.yellow(`Available profiles: ${available.join(', ')}`));
|
|
530
|
+
}
|
|
531
|
+
console.log(chalk.yellow('Run "confluence init --profile <name>" to create it, or "confluence profile list" to see available profiles.'));
|
|
389
532
|
process.exit(1);
|
|
390
533
|
}
|
|
391
534
|
|
|
392
535
|
try {
|
|
393
|
-
const storedConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
394
536
|
const trimmedDomain = (storedConfig.domain || '').trim();
|
|
395
537
|
const trimmedToken = (storedConfig.token || '').trim();
|
|
396
538
|
const trimmedEmail = storedConfig.email ? storedConfig.email.trim() : undefined;
|
|
@@ -404,8 +546,8 @@ function getConfig() {
|
|
|
404
546
|
}
|
|
405
547
|
|
|
406
548
|
if (authType === 'basic' && !trimmedEmail) {
|
|
407
|
-
console.error(chalk.red('❌ Basic authentication requires an email address.'));
|
|
408
|
-
console.log(chalk.yellow('Please rerun "confluence init" to add your Confluence email.'));
|
|
549
|
+
console.error(chalk.red('❌ Basic authentication requires an email address or username.'));
|
|
550
|
+
console.log(chalk.yellow('Please rerun "confluence init" to add your Confluence email or username.'));
|
|
409
551
|
process.exit(1);
|
|
410
552
|
}
|
|
411
553
|
|
|
@@ -417,12 +559,18 @@ function getConfig() {
|
|
|
417
559
|
process.exit(1);
|
|
418
560
|
}
|
|
419
561
|
|
|
562
|
+
const readOnly = envReadOnly !== undefined
|
|
563
|
+
? envReadOnly === 'true'
|
|
564
|
+
: Boolean(storedConfig.readOnly);
|
|
565
|
+
|
|
420
566
|
return {
|
|
421
567
|
domain: trimmedDomain,
|
|
568
|
+
protocol: normalizeProtocol(storedConfig.protocol),
|
|
422
569
|
apiPath,
|
|
423
570
|
token: trimmedToken,
|
|
424
571
|
email: trimmedEmail,
|
|
425
|
-
authType
|
|
572
|
+
authType,
|
|
573
|
+
readOnly
|
|
426
574
|
};
|
|
427
575
|
} catch (error) {
|
|
428
576
|
console.error(chalk.red('❌ Error reading configuration file:'), error.message);
|
|
@@ -431,7 +579,61 @@ function getConfig() {
|
|
|
431
579
|
}
|
|
432
580
|
}
|
|
433
581
|
|
|
582
|
+
function listProfiles() {
|
|
583
|
+
const fileData = readConfigFile();
|
|
584
|
+
if (!fileData || !fileData.profiles || Object.keys(fileData.profiles).length === 0) {
|
|
585
|
+
return { activeProfile: null, profiles: [] };
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
activeProfile: fileData.activeProfile,
|
|
589
|
+
profiles: Object.keys(fileData.profiles).map(name => ({
|
|
590
|
+
name,
|
|
591
|
+
active: name === fileData.activeProfile,
|
|
592
|
+
domain: fileData.profiles[name].domain,
|
|
593
|
+
readOnly: Boolean(fileData.profiles[name].readOnly)
|
|
594
|
+
}))
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function setActiveProfile(profileName) {
|
|
599
|
+
const fileData = readConfigFile();
|
|
600
|
+
if (!fileData) {
|
|
601
|
+
throw new Error('No configuration file found. Run "confluence init" first.');
|
|
602
|
+
}
|
|
603
|
+
if (!fileData.profiles || !fileData.profiles[profileName]) {
|
|
604
|
+
const available = fileData.profiles ? Object.keys(fileData.profiles) : [];
|
|
605
|
+
throw new Error(`Profile "${profileName}" not found. Available: ${available.join(', ')}`);
|
|
606
|
+
}
|
|
607
|
+
fileData.activeProfile = profileName;
|
|
608
|
+
saveConfigFile(fileData);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function deleteProfile(profileName) {
|
|
612
|
+
const fileData = readConfigFile();
|
|
613
|
+
if (!fileData) {
|
|
614
|
+
throw new Error('No configuration file found. Run "confluence init" first.');
|
|
615
|
+
}
|
|
616
|
+
if (!fileData.profiles || !fileData.profiles[profileName]) {
|
|
617
|
+
throw new Error(`Profile "${profileName}" not found.`);
|
|
618
|
+
}
|
|
619
|
+
if (Object.keys(fileData.profiles).length === 1) {
|
|
620
|
+
throw new Error('Cannot delete the only remaining profile.');
|
|
621
|
+
}
|
|
622
|
+
delete fileData.profiles[profileName];
|
|
623
|
+
if (fileData.activeProfile === profileName) {
|
|
624
|
+
fileData.activeProfile = Object.keys(fileData.profiles)[0];
|
|
625
|
+
}
|
|
626
|
+
saveConfigFile(fileData);
|
|
627
|
+
}
|
|
628
|
+
|
|
434
629
|
module.exports = {
|
|
435
630
|
initConfig,
|
|
436
|
-
getConfig
|
|
631
|
+
getConfig,
|
|
632
|
+
listProfiles,
|
|
633
|
+
setActiveProfile,
|
|
634
|
+
deleteProfile,
|
|
635
|
+
isValidProfileName,
|
|
636
|
+
CONFIG_DIR,
|
|
637
|
+
CONFIG_FILE,
|
|
638
|
+
DEFAULT_PROFILE
|
|
437
639
|
};
|