confluence-cli 1.30.1 → 1.30.2
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 +21 -0
- package/bin/confluence.js +50 -74
- package/lib/config.js +52 -42
- package/lib/confluence-client.js +5 -4
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/plugins/confluence/skills/confluence/SKILL.md +2 -0
package/README.md
CHANGED
|
@@ -204,6 +204,27 @@ export CONFLUENCE_API_TOKEN="your-scoped-token"
|
|
|
204
204
|
|
|
205
205
|
`CONFLUENCE_API_PATH` defaults to `/wiki/rest/api` for Atlassian Cloud domains and `/rest/api` otherwise. Override it when your site lives under a custom reverse proxy or on-premises path. `CONFLUENCE_AUTH_TYPE` defaults to `basic` when an email is present and falls back to `bearer` otherwise. For `mtls`, set `CONFLUENCE_TLS_CLIENT_CERT` and `CONFLUENCE_TLS_CLIENT_KEY`; `CONFLUENCE_TLS_CA_CERT` is optional.
|
|
206
206
|
|
|
207
|
+
**Custom domains on Confluence Cloud:**
|
|
208
|
+
|
|
209
|
+
If your Confluence Cloud instance uses a custom domain (e.g., `wiki.example.org` instead of `*.atlassian.net`), the CLI may misidentify it as a Server/Data Center instance and produce broken link formats. Set `CONFLUENCE_FORCE_CLOUD=true` to override the automatic detection:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
export CONFLUENCE_FORCE_CLOUD=true
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Or add `"forceCloud": true` to your profile in `~/.confluence-cli/config.json`:
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"profiles": {
|
|
220
|
+
"default": {
|
|
221
|
+
"domain": "wiki.example.org",
|
|
222
|
+
"forceCloud": true
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
207
228
|
**Read-only mode** (recommended for AI agents):
|
|
208
229
|
```bash
|
|
209
230
|
export CONFLUENCE_READ_ONLY=true
|
package/bin/confluence.js
CHANGED
|
@@ -16,6 +16,18 @@ function assertWritable(config) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function assertNonEmpty(value, label) {
|
|
20
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
21
|
+
throw new Error(`${label} is required and cannot be empty.`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function handleCommandError(analytics, commandName, error) {
|
|
26
|
+
analytics.track(commandName, false);
|
|
27
|
+
console.error(chalk.red('Error:'), error.message);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
program
|
|
20
32
|
.name('confluence')
|
|
21
33
|
.description('CLI tool for Atlassian Confluence')
|
|
@@ -59,9 +71,7 @@ program
|
|
|
59
71
|
console.log(content);
|
|
60
72
|
analytics.track('read', true);
|
|
61
73
|
} catch (error) {
|
|
62
|
-
analytics
|
|
63
|
-
console.error(chalk.red('Error:'), error.message);
|
|
64
|
-
process.exit(1);
|
|
74
|
+
handleCommandError(analytics, 'read', error);
|
|
65
75
|
}
|
|
66
76
|
});
|
|
67
77
|
|
|
@@ -84,9 +94,7 @@ program
|
|
|
84
94
|
}
|
|
85
95
|
analytics.track('info', true);
|
|
86
96
|
} catch (error) {
|
|
87
|
-
analytics
|
|
88
|
-
console.error(chalk.red('Error:'), error.message);
|
|
89
|
-
process.exit(1);
|
|
97
|
+
handleCommandError(analytics, 'info', error);
|
|
90
98
|
}
|
|
91
99
|
});
|
|
92
100
|
|
|
@@ -117,9 +125,7 @@ program
|
|
|
117
125
|
});
|
|
118
126
|
analytics.track('search', true);
|
|
119
127
|
} catch (error) {
|
|
120
|
-
analytics
|
|
121
|
-
console.error(chalk.red('Error:'), error.message);
|
|
122
|
-
process.exit(1);
|
|
128
|
+
handleCommandError(analytics, 'search', error);
|
|
123
129
|
}
|
|
124
130
|
});
|
|
125
131
|
|
|
@@ -140,9 +146,7 @@ program
|
|
|
140
146
|
});
|
|
141
147
|
analytics.track('spaces', true);
|
|
142
148
|
} catch (error) {
|
|
143
|
-
analytics
|
|
144
|
-
console.error(chalk.red('Error:'), error.message);
|
|
145
|
-
process.exit(1);
|
|
149
|
+
handleCommandError(analytics, 'spaces', error);
|
|
146
150
|
}
|
|
147
151
|
});
|
|
148
152
|
|
|
@@ -214,12 +218,15 @@ program
|
|
|
214
218
|
.action(async (title, spaceKey, options) => {
|
|
215
219
|
const analytics = new Analytics();
|
|
216
220
|
try {
|
|
221
|
+
assertNonEmpty(title, 'title');
|
|
222
|
+
assertNonEmpty(spaceKey, 'spaceKey');
|
|
223
|
+
|
|
217
224
|
const config = getConfig(getProfileName());
|
|
218
225
|
assertWritable(config);
|
|
219
226
|
const client = new ConfluenceClient(config);
|
|
220
227
|
|
|
221
228
|
let content = '';
|
|
222
|
-
|
|
229
|
+
|
|
223
230
|
if (options.file) {
|
|
224
231
|
const fs = require('fs');
|
|
225
232
|
if (!fs.existsSync(options.file)) {
|
|
@@ -231,7 +238,7 @@ program
|
|
|
231
238
|
} else {
|
|
232
239
|
throw new Error('Either --file or --content option is required');
|
|
233
240
|
}
|
|
234
|
-
|
|
241
|
+
|
|
235
242
|
const result = await client.createPage(title, spaceKey, content, options.format);
|
|
236
243
|
|
|
237
244
|
console.log(chalk.green('✅ Page created successfully!'));
|
|
@@ -242,9 +249,7 @@ program
|
|
|
242
249
|
|
|
243
250
|
analytics.track('create', true);
|
|
244
251
|
} catch (error) {
|
|
245
|
-
analytics
|
|
246
|
-
console.error(chalk.red('Error:'), error.message);
|
|
247
|
-
process.exit(1);
|
|
252
|
+
handleCommandError(analytics, 'create', error);
|
|
248
253
|
}
|
|
249
254
|
});
|
|
250
255
|
|
|
@@ -258,6 +263,9 @@ program
|
|
|
258
263
|
.action(async (title, parentId, options) => {
|
|
259
264
|
const analytics = new Analytics();
|
|
260
265
|
try {
|
|
266
|
+
assertNonEmpty(title, 'title');
|
|
267
|
+
assertNonEmpty(parentId, 'parentId');
|
|
268
|
+
|
|
261
269
|
const config = getConfig(getProfileName());
|
|
262
270
|
assertWritable(config);
|
|
263
271
|
const client = new ConfluenceClient(config);
|
|
@@ -291,9 +299,7 @@ program
|
|
|
291
299
|
|
|
292
300
|
analytics.track('create_child', true);
|
|
293
301
|
} catch (error) {
|
|
294
|
-
analytics
|
|
295
|
-
console.error(chalk.red('Error:'), error.message);
|
|
296
|
-
process.exit(1);
|
|
302
|
+
handleCommandError(analytics, 'create_child', error);
|
|
297
303
|
}
|
|
298
304
|
});
|
|
299
305
|
|
|
@@ -313,6 +319,10 @@ program
|
|
|
313
319
|
throw new Error('At least one of --title, --file, or --content must be provided.');
|
|
314
320
|
}
|
|
315
321
|
|
|
322
|
+
if (options.title !== undefined) {
|
|
323
|
+
assertNonEmpty(options.title, '--title');
|
|
324
|
+
}
|
|
325
|
+
|
|
316
326
|
const config = getConfig(getProfileName());
|
|
317
327
|
assertWritable(config);
|
|
318
328
|
const client = new ConfluenceClient(config);
|
|
@@ -339,9 +349,7 @@ program
|
|
|
339
349
|
|
|
340
350
|
analytics.track('update', true);
|
|
341
351
|
} catch (error) {
|
|
342
|
-
analytics
|
|
343
|
-
console.error(chalk.red('Error:'), error.message);
|
|
344
|
-
process.exit(1);
|
|
352
|
+
handleCommandError(analytics, 'update', error);
|
|
345
353
|
}
|
|
346
354
|
});
|
|
347
355
|
|
|
@@ -367,9 +375,7 @@ program
|
|
|
367
375
|
|
|
368
376
|
analytics.track('move', true);
|
|
369
377
|
} catch (error) {
|
|
370
|
-
analytics
|
|
371
|
-
console.error(chalk.red('Error:'), error.message);
|
|
372
|
-
process.exit(1);
|
|
378
|
+
handleCommandError(analytics, 'move', error);
|
|
373
379
|
}
|
|
374
380
|
});
|
|
375
381
|
|
|
@@ -411,9 +417,7 @@ program
|
|
|
411
417
|
console.log(`ID: ${chalk.blue(result.id)}`);
|
|
412
418
|
analytics.track('delete', true);
|
|
413
419
|
} catch (error) {
|
|
414
|
-
analytics
|
|
415
|
-
console.error(chalk.red('Error:'), error.message);
|
|
416
|
-
process.exit(1);
|
|
420
|
+
handleCommandError(analytics, 'delete', error);
|
|
417
421
|
}
|
|
418
422
|
});
|
|
419
423
|
|
|
@@ -449,9 +453,7 @@ program
|
|
|
449
453
|
|
|
450
454
|
analytics.track('edit', true);
|
|
451
455
|
} catch (error) {
|
|
452
|
-
analytics
|
|
453
|
-
console.error(chalk.red('Error:'), error.message);
|
|
454
|
-
process.exit(1);
|
|
456
|
+
handleCommandError(analytics, 'edit', error);
|
|
455
457
|
}
|
|
456
458
|
});
|
|
457
459
|
|
|
@@ -475,9 +477,7 @@ program
|
|
|
475
477
|
|
|
476
478
|
analytics.track('find', true);
|
|
477
479
|
} catch (error) {
|
|
478
|
-
analytics
|
|
479
|
-
console.error(chalk.red('Error:'), error.message);
|
|
480
|
-
process.exit(1);
|
|
480
|
+
handleCommandError(analytics, 'find', error);
|
|
481
481
|
}
|
|
482
482
|
});
|
|
483
483
|
|
|
@@ -597,9 +597,7 @@ program
|
|
|
597
597
|
|
|
598
598
|
analytics.track('attachments', true);
|
|
599
599
|
} catch (error) {
|
|
600
|
-
analytics
|
|
601
|
-
console.error(chalk.red('Error:'), error.message);
|
|
602
|
-
process.exit(1);
|
|
600
|
+
handleCommandError(analytics, 'attachments', error);
|
|
603
601
|
}
|
|
604
602
|
});
|
|
605
603
|
|
|
@@ -659,9 +657,7 @@ program
|
|
|
659
657
|
console.log(chalk.green(`Uploaded ${uploaded} attachment${uploaded === 1 ? '' : 's'} to page ${pageId}`));
|
|
660
658
|
analytics.track('attachment_upload', true);
|
|
661
659
|
} catch (error) {
|
|
662
|
-
analytics
|
|
663
|
-
console.error(chalk.red('Error:'), error.message);
|
|
664
|
-
process.exit(1);
|
|
660
|
+
handleCommandError(analytics, 'attachment_upload', error);
|
|
665
661
|
}
|
|
666
662
|
});
|
|
667
663
|
|
|
@@ -701,9 +697,7 @@ program
|
|
|
701
697
|
console.log(`Page ID: ${chalk.blue(result.pageId)}`);
|
|
702
698
|
analytics.track('attachment_delete', true);
|
|
703
699
|
} catch (error) {
|
|
704
|
-
analytics
|
|
705
|
-
console.error(chalk.red('Error:'), error.message);
|
|
706
|
-
process.exit(1);
|
|
700
|
+
handleCommandError(analytics, 'attachment_delete', error);
|
|
707
701
|
}
|
|
708
702
|
});
|
|
709
703
|
|
|
@@ -774,9 +768,7 @@ program
|
|
|
774
768
|
}
|
|
775
769
|
analytics.track('property_list', true);
|
|
776
770
|
} catch (error) {
|
|
777
|
-
analytics
|
|
778
|
-
console.error(chalk.red('Error:'), error.message);
|
|
779
|
-
process.exit(1);
|
|
771
|
+
handleCommandError(analytics, 'property_list', error);
|
|
780
772
|
}
|
|
781
773
|
});
|
|
782
774
|
|
|
@@ -808,9 +800,7 @@ program
|
|
|
808
800
|
}
|
|
809
801
|
analytics.track('property_get', true);
|
|
810
802
|
} catch (error) {
|
|
811
|
-
analytics
|
|
812
|
-
console.error(chalk.red('Error:'), error.message);
|
|
813
|
-
process.exit(1);
|
|
803
|
+
handleCommandError(analytics, 'property_get', error);
|
|
814
804
|
}
|
|
815
805
|
});
|
|
816
806
|
|
|
@@ -867,9 +857,7 @@ program
|
|
|
867
857
|
}
|
|
868
858
|
analytics.track('property_set', true);
|
|
869
859
|
} catch (error) {
|
|
870
|
-
analytics
|
|
871
|
-
console.error(chalk.red('Error:'), error.message);
|
|
872
|
-
process.exit(1);
|
|
860
|
+
handleCommandError(analytics, 'property_set', error);
|
|
873
861
|
}
|
|
874
862
|
});
|
|
875
863
|
|
|
@@ -909,9 +897,7 @@ program
|
|
|
909
897
|
console.log(`${chalk.green('Page ID:')} ${chalk.blue(result.pageId)}`);
|
|
910
898
|
analytics.track('property_delete', true);
|
|
911
899
|
} catch (error) {
|
|
912
|
-
analytics
|
|
913
|
-
console.error(chalk.red('Error:'), error.message);
|
|
914
|
-
process.exit(1);
|
|
900
|
+
handleCommandError(analytics, 'property_delete', error);
|
|
915
901
|
}
|
|
916
902
|
});
|
|
917
903
|
|
|
@@ -1060,9 +1046,7 @@ program
|
|
|
1060
1046
|
|
|
1061
1047
|
analytics.track('comments', true);
|
|
1062
1048
|
} catch (error) {
|
|
1063
|
-
analytics
|
|
1064
|
-
console.error(chalk.red('Error:'), error.message);
|
|
1065
|
-
process.exit(1);
|
|
1049
|
+
handleCommandError(analytics, 'comments', error);
|
|
1066
1050
|
}
|
|
1067
1051
|
});
|
|
1068
1052
|
|
|
@@ -1225,9 +1209,7 @@ program
|
|
|
1225
1209
|
console.log(`ID: ${chalk.blue(result.id)}`);
|
|
1226
1210
|
analytics.track('comment_delete', true);
|
|
1227
1211
|
} catch (error) {
|
|
1228
|
-
analytics
|
|
1229
|
-
console.error(chalk.red('Error:'), error.message);
|
|
1230
|
-
process.exit(1);
|
|
1212
|
+
handleCommandError(analytics, 'comment_delete', error);
|
|
1231
1213
|
}
|
|
1232
1214
|
});
|
|
1233
1215
|
|
|
@@ -1332,9 +1314,7 @@ program
|
|
|
1332
1314
|
|
|
1333
1315
|
analytics.track('export', true);
|
|
1334
1316
|
} catch (error) {
|
|
1335
|
-
analytics
|
|
1336
|
-
console.error(chalk.red('Error:'), error.message);
|
|
1337
|
-
process.exit(1);
|
|
1317
|
+
handleCommandError(analytics, 'export', error);
|
|
1338
1318
|
}
|
|
1339
1319
|
});
|
|
1340
1320
|
|
|
@@ -1720,9 +1700,7 @@ program
|
|
|
1720
1700
|
|
|
1721
1701
|
analytics.track('copy_tree', true);
|
|
1722
1702
|
} catch (error) {
|
|
1723
|
-
analytics
|
|
1724
|
-
console.error(chalk.red('Error:'), error.message);
|
|
1725
|
-
process.exit(1);
|
|
1703
|
+
handleCommandError(analytics, 'copy_tree', error);
|
|
1726
1704
|
}
|
|
1727
1705
|
});
|
|
1728
1706
|
|
|
@@ -1819,9 +1797,7 @@ program
|
|
|
1819
1797
|
|
|
1820
1798
|
analytics.track('children', true);
|
|
1821
1799
|
} catch (error) {
|
|
1822
|
-
analytics
|
|
1823
|
-
console.error(chalk.red('Error:'), error.message);
|
|
1824
|
-
process.exit(1);
|
|
1800
|
+
handleCommandError(analytics, 'children', error);
|
|
1825
1801
|
}
|
|
1826
1802
|
});
|
|
1827
1803
|
|
|
@@ -2039,9 +2015,7 @@ program
|
|
|
2039
2015
|
}
|
|
2040
2016
|
analytics.track('convert', true);
|
|
2041
2017
|
} catch (error) {
|
|
2042
|
-
analytics
|
|
2043
|
-
console.error(chalk.red('Error:'), error.message);
|
|
2044
|
-
process.exit(1);
|
|
2018
|
+
handleCommandError(analytics, 'convert', error);
|
|
2045
2019
|
}
|
|
2046
2020
|
});
|
|
2047
2021
|
|
|
@@ -2056,6 +2030,8 @@ module.exports = {
|
|
|
2056
2030
|
exportRecursive,
|
|
2057
2031
|
sanitizeTitle,
|
|
2058
2032
|
assertWritable,
|
|
2033
|
+
assertNonEmpty,
|
|
2034
|
+
handleCommandError,
|
|
2059
2035
|
},
|
|
2060
2036
|
};
|
|
2061
2037
|
|
package/lib/config.js
CHANGED
|
@@ -100,6 +100,30 @@ const validateMtlsProtocol = (protocol) => {
|
|
|
100
100
|
return null;
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
+
// Validate a resolved auth configuration (post-normalization).
|
|
104
|
+
// Returns error messages only; callers format source-specific hints.
|
|
105
|
+
const validateAuthConfig = (auth, mtlsSourceLabel) => {
|
|
106
|
+
const errors = [];
|
|
107
|
+
|
|
108
|
+
if (auth.authType === 'basic' && !auth.email) {
|
|
109
|
+
errors.push('Basic authentication requires an email address or username.');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (auth.authType !== 'mtls' && !auth.token) {
|
|
113
|
+
errors.push('Bearer or basic authentication requires a token.');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (auth.authType === 'mtls') {
|
|
117
|
+
errors.push(...validateMtlsConfig(auth.mtls, mtlsSourceLabel));
|
|
118
|
+
const protocolError = validateMtlsProtocol(auth.protocol);
|
|
119
|
+
if (protocolError) {
|
|
120
|
+
errors.push(protocolError);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return errors;
|
|
125
|
+
};
|
|
126
|
+
|
|
103
127
|
/**
|
|
104
128
|
* Build an inquirer question for an mTLS file path prompt.
|
|
105
129
|
* @param {string} name - answer key (e.g. 'tlsClientCert')
|
|
@@ -637,6 +661,7 @@ function getConfig(profileName) {
|
|
|
637
661
|
const envApiPath = process.env.CONFLUENCE_API_PATH;
|
|
638
662
|
const envProtocol = process.env.CONFLUENCE_PROTOCOL;
|
|
639
663
|
const envReadOnly = process.env.CONFLUENCE_READ_ONLY;
|
|
664
|
+
const envForceCloud = process.env.CONFLUENCE_FORCE_CLOUD;
|
|
640
665
|
const envMtls = normalizeMtlsConfig({
|
|
641
666
|
caCert: process.env.CONFLUENCE_TLS_CA_CERT,
|
|
642
667
|
clientCert: process.env.CONFLUENCE_TLS_CLIENT_CERT,
|
|
@@ -655,29 +680,19 @@ function getConfig(profileName) {
|
|
|
655
680
|
process.exit(1);
|
|
656
681
|
}
|
|
657
682
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
process.exit(1);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
if (authType === 'mtls') {
|
|
670
|
-
const mtlsErrors = validateMtlsConfig(envMtls, 'CONFLUENCE_AUTH_TYPE=mtls');
|
|
671
|
-
if (mtlsErrors.length > 0) {
|
|
672
|
-
console.error(chalk.red(`❌ ${mtlsErrors.join(' ')}`));
|
|
673
|
-
console.log(chalk.yellow('Set CONFLUENCE_TLS_CLIENT_CERT and CONFLUENCE_TLS_CLIENT_KEY. Optionally set CONFLUENCE_TLS_CA_CERT.'));
|
|
674
|
-
process.exit(1);
|
|
683
|
+
const authErrors = validateAuthConfig(
|
|
684
|
+
{ authType, token: envToken, email: envEmail, mtls: envMtls, protocol: envProtocol },
|
|
685
|
+
'CONFLUENCE_AUTH_TYPE=mtls'
|
|
686
|
+
);
|
|
687
|
+
if (authErrors.length > 0) {
|
|
688
|
+
console.error(chalk.red(`❌ ${authErrors.join(' ')}`));
|
|
689
|
+
if (authType === 'basic' && !envEmail) {
|
|
690
|
+
console.log(chalk.yellow('Set CONFLUENCE_EMAIL (or CONFLUENCE_USERNAME for on-premise) or switch to bearer auth by setting CONFLUENCE_AUTH_TYPE=bearer.'));
|
|
675
691
|
}
|
|
676
|
-
if (
|
|
677
|
-
|
|
678
|
-
console.error(chalk.red(`❌ ${protocolError}`));
|
|
679
|
-
process.exit(1);
|
|
692
|
+
if (authType === 'mtls' && !envMtls) {
|
|
693
|
+
console.log(chalk.yellow('Set CONFLUENCE_TLS_CLIENT_CERT and CONFLUENCE_TLS_CLIENT_KEY. Optionally set CONFLUENCE_TLS_CA_CERT.'));
|
|
680
694
|
}
|
|
695
|
+
process.exit(1);
|
|
681
696
|
}
|
|
682
697
|
|
|
683
698
|
return {
|
|
@@ -688,7 +703,8 @@ function getConfig(profileName) {
|
|
|
688
703
|
email: envEmail ? envEmail.trim() : undefined,
|
|
689
704
|
authType,
|
|
690
705
|
mtls: envMtls,
|
|
691
|
-
readOnly: envReadOnly === 'true'
|
|
706
|
+
readOnly: envReadOnly === 'true',
|
|
707
|
+
forceCloud: envForceCloud === 'true'
|
|
692
708
|
};
|
|
693
709
|
}
|
|
694
710
|
|
|
@@ -727,33 +743,22 @@ function getConfig(profileName) {
|
|
|
727
743
|
const mtls = normalizeMtlsConfig(storedConfig.mtls);
|
|
728
744
|
let apiPath;
|
|
729
745
|
|
|
730
|
-
if (!trimmedDomain
|
|
746
|
+
if (!trimmedDomain) {
|
|
731
747
|
console.error(chalk.red('❌ Configuration file is missing required values.'));
|
|
732
748
|
console.log(chalk.yellow('Run "confluence init" to refresh your settings.'));
|
|
733
749
|
process.exit(1);
|
|
734
750
|
}
|
|
735
751
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
752
|
+
const authErrors = validateAuthConfig(
|
|
753
|
+
{ authType, token: trimmedToken, email: trimmedEmail, mtls, protocol: storedConfig.protocol },
|
|
754
|
+
'mTLS authentication'
|
|
755
|
+
);
|
|
756
|
+
if (authErrors.length > 0) {
|
|
757
|
+
console.error(chalk.red(`❌ ${authErrors.join(' ')}`));
|
|
758
|
+
console.log(chalk.yellow('Please rerun "confluence init" to refresh your settings.'));
|
|
739
759
|
process.exit(1);
|
|
740
760
|
}
|
|
741
761
|
|
|
742
|
-
if (authType === 'mtls') {
|
|
743
|
-
const mtlsErrors = validateMtlsConfig(mtls, 'mTLS authentication');
|
|
744
|
-
if (mtlsErrors.length > 0) {
|
|
745
|
-
console.error(chalk.red(`❌ ${mtlsErrors.join(' ')}`));
|
|
746
|
-
console.log(chalk.yellow('Please rerun "confluence init" to add your mTLS certificate paths.'));
|
|
747
|
-
process.exit(1);
|
|
748
|
-
}
|
|
749
|
-
if (normalizeProtocol(storedConfig.protocol) === 'http') {
|
|
750
|
-
const protocolError = validateMtlsProtocol(storedConfig.protocol);
|
|
751
|
-
console.error(chalk.red(`❌ ${protocolError}`));
|
|
752
|
-
console.log(chalk.yellow('Please rerun "confluence init" to update your protocol to HTTPS.'));
|
|
753
|
-
process.exit(1);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
762
|
try {
|
|
758
763
|
apiPath = normalizeApiPath(storedConfig.apiPath, trimmedDomain);
|
|
759
764
|
} catch (error) {
|
|
@@ -766,6 +771,10 @@ function getConfig(profileName) {
|
|
|
766
771
|
? envReadOnly === 'true'
|
|
767
772
|
: Boolean(storedConfig.readOnly);
|
|
768
773
|
|
|
774
|
+
const forceCloud = envForceCloud !== undefined
|
|
775
|
+
? envForceCloud === 'true'
|
|
776
|
+
: Boolean(storedConfig.forceCloud);
|
|
777
|
+
|
|
769
778
|
return {
|
|
770
779
|
domain: trimmedDomain,
|
|
771
780
|
protocol: normalizeProtocol(storedConfig.protocol),
|
|
@@ -774,7 +783,8 @@ function getConfig(profileName) {
|
|
|
774
783
|
email: trimmedEmail,
|
|
775
784
|
authType,
|
|
776
785
|
mtls,
|
|
777
|
-
readOnly
|
|
786
|
+
readOnly,
|
|
787
|
+
forceCloud
|
|
778
788
|
};
|
|
779
789
|
} catch (error) {
|
|
780
790
|
console.error(chalk.red('❌ Error reading configuration file:'), error.message);
|
package/lib/confluence-client.js
CHANGED
|
@@ -35,6 +35,7 @@ class ConfluenceClient {
|
|
|
35
35
|
this.token = config.token;
|
|
36
36
|
this.email = config.email;
|
|
37
37
|
this.authType = (config.authType || (this.email ? 'basic' : 'bearer')).toLowerCase();
|
|
38
|
+
this.forceCloud = !!config.forceCloud;
|
|
38
39
|
this.mtls = config.mtls;
|
|
39
40
|
this.apiPath = this.sanitizeApiPath(config.apiPath);
|
|
40
41
|
this.webUrlPrefix = this.apiPath.startsWith('/wiki/') ? '/wiki' : '';
|
|
@@ -100,7 +101,7 @@ class ConfluenceClient {
|
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
isCloud() {
|
|
103
|
-
return this.isScopedToken() || (this.domain && this.domain.trim().toLowerCase().endsWith('.atlassian.net'));
|
|
104
|
+
return this.isScopedToken() || (this.domain && this.domain.trim().toLowerCase().endsWith('.atlassian.net')) || this.forceCloud;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
isScopedToken() {
|
|
@@ -477,12 +478,12 @@ class ConfluenceClient {
|
|
|
477
478
|
// Replace userkey references with display names in HTML
|
|
478
479
|
let resolvedHtml = html;
|
|
479
480
|
userMap.forEach((displayName, userKey) => {
|
|
480
|
-
|
|
481
|
+
const escapedKey = userKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
481
482
|
const userLinkRegex = new RegExp(
|
|
482
|
-
`<ac:link>\\s*<ri:user\\s+ri:userkey="${
|
|
483
|
+
`<ac:link>\\s*<ri:user\\s+ri:userkey="${escapedKey}"\\s*/>\\s*</ac:link>`,
|
|
483
484
|
'g'
|
|
484
485
|
);
|
|
485
|
-
resolvedHtml = resolvedHtml.replace(userLinkRegex, `@${displayName}`);
|
|
486
|
+
resolvedHtml = resolvedHtml.replace(userLinkRegex, () => `@${displayName}`);
|
|
486
487
|
});
|
|
487
488
|
|
|
488
489
|
return { html: resolvedHtml, userMap };
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "confluence-cli",
|
|
3
|
-
"version": "1.30.
|
|
3
|
+
"version": "1.30.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "confluence-cli",
|
|
9
|
-
"version": "1.30.
|
|
9
|
+
"version": "1.30.2",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"axios": "^1.15.0",
|
package/package.json
CHANGED
|
@@ -29,6 +29,7 @@ confluence --version # verify install
|
|
|
29
29
|
| `CONFLUENCE_API_TOKEN` | API token or personal access token | `ATATT3x...` |
|
|
30
30
|
| `CONFLUENCE_PROFILE` | Named profile to use (optional) | `staging` |
|
|
31
31
|
| `CONFLUENCE_READ_ONLY` | Block all write operations when `true` | `true` |
|
|
32
|
+
| `CONFLUENCE_FORCE_CLOUD` | Force Cloud link format for custom domains | `true` |
|
|
32
33
|
|
|
33
34
|
**Global `--profile` flag (use a named profile for any command):**
|
|
34
35
|
|
|
@@ -53,6 +54,7 @@ confluence init \
|
|
|
53
54
|
|
|
54
55
|
**Cloud vs Server/DC:**
|
|
55
56
|
- Atlassian Cloud (`*.atlassian.net`): use `--api-path "/wiki/rest/api"`, auth type `basic` with email + API token
|
|
57
|
+
- Atlassian Cloud (custom domain): if your Cloud instance uses a custom domain (e.g., `wiki.example.org`), set `CONFLUENCE_FORCE_CLOUD=true` or add `"forceCloud": true` to your profile in `~/.confluence-cli/config.json`. Without this, links will render incorrectly.
|
|
56
58
|
- Atlassian Cloud (scoped token): use `--domain "api.atlassian.com"`, `--api-path "/ex/confluence/<your-cloud-id>/wiki/rest/api"`, auth type `basic` with email + scoped token. Get your Cloud ID from `https://<your-site>.atlassian.net/_edge/tenant_info`. Recommended for agents (least privilege).
|
|
57
59
|
- Self-hosted / Data Center: use `--api-path "/rest/api"`, auth type `bearer` with a personal access token (no email needed)
|
|
58
60
|
|