confluence-cli 1.32.0 → 1.33.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/README.md CHANGED
@@ -249,6 +249,30 @@ Or add `"forceCloud": true` to your profile in `~/.confluence-cli/config.json`:
249
249
  }
250
250
  ```
251
251
 
252
+ **Link rendering on Cloud (`linkStyle`):**
253
+
254
+ Some Cloud instances — particularly custom-domain Cloud setups — fail to render smart links (`<a data-card-appearance="inline">`) and show "Cannot handle: DefaultLink" errors instead. If you hit this, set `linkStyle` to `plain` to emit simple `<a href>` tags, which render reliably everywhere:
255
+
256
+ ```bash
257
+ export CONFLUENCE_LINK_STYLE=plain
258
+ ```
259
+
260
+ Or per-profile:
261
+
262
+ ```json
263
+ {
264
+ "profiles": {
265
+ "default": {
266
+ "domain": "wiki.example.org",
267
+ "forceCloud": true,
268
+ "linkStyle": "plain"
269
+ }
270
+ }
271
+ }
272
+ ```
273
+
274
+ Valid values: `smart` (Cloud smart links), `plain` (simple `<a href>`), `wiki` (Server/DC `ac:link`). When unset, the CLI picks `smart` for Cloud and `wiki` for Server/DC — existing behavior is unchanged.
275
+
252
276
  **Read-only mode** (recommended for AI agents):
253
277
  ```bash
254
278
  export CONFLUENCE_READ_ONLY=true
package/lib/config.js CHANGED
@@ -17,6 +17,23 @@ const AUTH_CHOICES = [
17
17
 
18
18
  const AUTH_TYPES = ['basic', 'bearer', 'mtls', 'cookie'];
19
19
 
20
+ const { VALID_LINK_STYLES } = require('./macro-converter');
21
+
22
+ const normalizeLinkStyle = (rawValue, source) => {
23
+ if (rawValue === undefined || rawValue === null || rawValue === '') {
24
+ return undefined;
25
+ }
26
+ const value = String(rawValue).trim().toLowerCase();
27
+ if (VALID_LINK_STYLES.includes(value)) {
28
+ return value;
29
+ }
30
+ const label = source ? `${source} ` : '';
31
+ console.error(chalk.yellow(
32
+ `⚠ Invalid linkStyle ${label}"${rawValue}"; valid values: ${VALID_LINK_STYLES.join(', ')}. Falling back to auto-detection.`
33
+ ));
34
+ return undefined;
35
+ };
36
+
20
37
  const isValidProfileName = (name) => /^[a-zA-Z0-9_-]+$/.test(name);
21
38
 
22
39
  const requiredInput = (label) => (input) => {
@@ -719,6 +736,7 @@ function getConfig(profileName) {
719
736
  const envProtocol = process.env.CONFLUENCE_PROTOCOL;
720
737
  const envReadOnly = process.env.CONFLUENCE_READ_ONLY;
721
738
  const envForceCloud = process.env.CONFLUENCE_FORCE_CLOUD;
739
+ const envLinkStyle = normalizeLinkStyle(process.env.CONFLUENCE_LINK_STYLE, 'from CONFLUENCE_LINK_STYLE');
722
740
  const envCookie = process.env.CONFLUENCE_COOKIE;
723
741
  const envMtls = normalizeMtlsConfig({
724
742
  caCert: process.env.CONFLUENCE_TLS_CA_CERT,
@@ -772,7 +790,8 @@ function getConfig(profileName) {
772
790
  authType,
773
791
  mtls: envMtls,
774
792
  readOnly: envReadOnly === 'true',
775
- forceCloud: envForceCloud === 'true'
793
+ forceCloud: envForceCloud === 'true',
794
+ linkStyle: envLinkStyle
776
795
  };
777
796
  }
778
797
 
@@ -844,6 +863,9 @@ function getConfig(profileName) {
844
863
  ? envForceCloud === 'true'
845
864
  : Boolean(storedConfig.forceCloud);
846
865
 
866
+ const linkStyle = envLinkStyle
867
+ ?? normalizeLinkStyle(storedConfig.linkStyle, `in profile "${targetProfile}"`);
868
+
847
869
  return {
848
870
  domain: trimmedDomain,
849
871
  protocol: normalizeProtocol(storedConfig.protocol),
@@ -854,7 +876,8 @@ function getConfig(profileName) {
854
876
  authType,
855
877
  mtls,
856
878
  readOnly,
857
- forceCloud
879
+ forceCloud,
880
+ linkStyle
858
881
  };
859
882
  } catch (error) {
860
883
  console.error(chalk.red('❌ Error reading configuration file:'), error.message);
@@ -47,6 +47,7 @@ class ConfluenceClient {
47
47
  isCloud: this.isCloud(),
48
48
  webUrlPrefix: this.webUrlPrefix,
49
49
  buildUrl: (pathOrUrl) => this.buildUrl(pathOrUrl),
50
+ linkStyle: config.linkStyle,
50
51
  });
51
52
  this.markdown = this.converter.markdown;
52
53
 
@@ -1,11 +1,16 @@
1
1
  const MarkdownIt = require('markdown-it');
2
2
  const { htmlToMarkdown } = require('./html-to-markdown');
3
3
 
4
+ const VALID_LINK_STYLES = ['smart', 'plain', 'wiki'];
5
+
4
6
  class MacroConverter {
5
- constructor({ isCloud = false, webUrlPrefix = '', buildUrl = null } = {}) {
7
+ constructor({ isCloud = false, webUrlPrefix = '', buildUrl = null, linkStyle = null } = {}) {
6
8
  this._isCloud = isCloud;
7
9
  this.webUrlPrefix = webUrlPrefix;
8
10
  this.buildUrl = buildUrl || ((pathOrUrl) => pathOrUrl);
11
+ this.linkStyle = VALID_LINK_STYLES.includes(linkStyle)
12
+ ? linkStyle
13
+ : (isCloud ? 'smart' : 'wiki');
9
14
  this.markdown = new MarkdownIt();
10
15
  this.setupConfluenceMarkdownExtensions();
11
16
  }
@@ -107,11 +112,17 @@ class MacroConverter {
107
112
  storage = storage.replace(/<th>(.*?)<\/th>/g, '<th><p>$1</p></th>');
108
113
  storage = storage.replace(/<td>(.*?)<\/td>/g, '<td><p>$1</p></td>');
109
114
 
110
- if (this.isCloud()) {
115
+ // Convert links based on linkStyle:
116
+ // "smart" — Cloud smart links (<a data-card-appearance="inline">)
117
+ // "plain" — simple <a href>; workaround for "Cannot handle: DefaultLink"
118
+ // errors on custom-domain Cloud instances
119
+ // "wiki" — Server/DC ac:link + ri:url storage format
120
+ if (this.linkStyle === 'smart') {
111
121
  storage = storage.replace(/<a href="(.*?)">(.*?)<\/a>/g, '<a href="$1" data-card-appearance="inline">$2</a>');
112
- } else {
122
+ } else if (this.linkStyle === 'wiki') {
113
123
  storage = storage.replace(/<a href="(.*?)">(.*?)<\/a>/g, '<ac:link><ri:url ri:value="$1" /><ac:plain-text-link-body><![CDATA[$2]]></ac:plain-text-link-body></ac:link>');
114
124
  }
125
+ // "plain" — leave <a href> tags as-is
115
126
 
116
127
  storage = storage.replace(/<hr\s*\/?>/g, '<hr />');
117
128
 
@@ -296,3 +307,4 @@ class MacroConverter {
296
307
  }
297
308
 
298
309
  module.exports = MacroConverter;
310
+ module.exports.VALID_LINK_STYLES = VALID_LINK_STYLES;