confluence-cli 1.7.0 → 1.8.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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # [1.8.0](https://github.com/pchuri/confluence-cli/compare/v1.7.0...v1.8.0) (2025-09-28)
2
+
3
+
4
+ ### Features
5
+
6
+ * make Confluence API path configurable ([#14](https://github.com/pchuri/confluence-cli/issues/14)) ([be000e0](https://github.com/pchuri/confluence-cli/commit/be000e0d92881d65329b84bad6555dcad0bbb455)), closes [#13](https://github.com/pchuri/confluence-cli/issues/13)
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - Make the Confluence REST base path configurable to support both `/rest/api` and `/wiki/rest/api`.
12
+
1
13
  # [1.7.0](https://github.com/pchuri/confluence-cli/compare/v1.6.0...v1.7.0) (2025-09-28)
2
14
 
3
15
 
package/README.md CHANGED
@@ -58,18 +58,19 @@ npx confluence-cli
58
58
  confluence init
59
59
  ```
60
60
 
61
- The wizard now asks for your authentication method. Choose **Basic** to supply your Atlassian email and API token, or switch to **Bearer** when working with self-hosted/Data Center environments.
61
+ The wizard now helps you choose the right API endpoint and authentication method. It recommends `/wiki/rest/api` for Atlassian Cloud domains (e.g., `*.atlassian.net`) and `/rest/api` for self-hosted/Data Center instances, then prompts for Basic (email + token) or Bearer authentication.
62
62
 
63
63
  ### Option 2: Environment Variables
64
64
  ```bash
65
65
  export CONFLUENCE_DOMAIN="your-domain.atlassian.net"
66
66
  export CONFLUENCE_API_TOKEN="your-api-token"
67
67
  export CONFLUENCE_EMAIL="your.email@example.com" # required when using Atlassian Cloud
68
+ export CONFLUENCE_API_PATH="/wiki/rest/api" # Cloud default; use /rest/api for Server/DC
68
69
  # Optional: set to 'bearer' for self-hosted/Data Center instances
69
70
  export CONFLUENCE_AUTH_TYPE="basic"
70
71
  ```
71
72
 
72
- `CONFLUENCE_AUTH_TYPE` defaults to `basic` when an email is present and falls back to `bearer` otherwise. Provide an email for Atlassian Cloud (Basic auth) or set `CONFLUENCE_AUTH_TYPE=bearer` to keep bearer-token flows for on-premises installations.
73
+ `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.
73
74
 
74
75
  ### Getting Your API Token
75
76
 
package/lib/config.js CHANGED
@@ -27,6 +27,34 @@ const normalizeAuthType = (rawValue, hasEmail) => {
27
27
  return hasEmail ? 'basic' : 'bearer';
28
28
  };
29
29
 
30
+ const inferApiPath = (domain) => {
31
+ if (!domain) {
32
+ return '/rest/api';
33
+ }
34
+
35
+ const normalizedDomain = domain.trim().toLowerCase();
36
+ if (normalizedDomain.endsWith('.atlassian.net')) {
37
+ return '/wiki/rest/api';
38
+ }
39
+
40
+ return '/rest/api';
41
+ };
42
+
43
+ const normalizeApiPath = (rawValue, domain) => {
44
+ const trimmed = (rawValue || '').trim();
45
+
46
+ if (!trimmed) {
47
+ return inferApiPath(domain);
48
+ }
49
+
50
+ if (!trimmed.startsWith('/')) {
51
+ throw new Error('Confluence API path must start with "/".');
52
+ }
53
+
54
+ const withoutTrailing = trimmed.replace(/\/+$/, '');
55
+ return withoutTrailing || inferApiPath(domain);
56
+ };
57
+
30
58
  async function initConfig() {
31
59
  console.log(chalk.blue('🚀 Confluence CLI Configuration'));
32
60
  console.log('Please provide your Confluence connection details:\n');
@@ -38,6 +66,27 @@ async function initConfig() {
38
66
  message: 'Confluence domain (e.g., yourcompany.atlassian.net):',
39
67
  validate: requiredInput('Domain')
40
68
  },
69
+ {
70
+ type: 'input',
71
+ name: 'apiPath',
72
+ message: 'REST API path (Cloud: /wiki/rest/api, Server: /rest/api):',
73
+ default: (responses) => inferApiPath(responses.domain),
74
+ validate: (input, responses) => {
75
+ const value = (input || '').trim();
76
+ if (!value) {
77
+ return true;
78
+ }
79
+ if (!value.startsWith('/')) {
80
+ return 'API path must start with "/"';
81
+ }
82
+ try {
83
+ normalizeApiPath(value, responses.domain);
84
+ return true;
85
+ } catch (error) {
86
+ return error.message;
87
+ }
88
+ }
89
+ },
41
90
  {
42
91
  type: 'list',
43
92
  name: 'authType',
@@ -66,6 +115,7 @@ async function initConfig() {
66
115
 
67
116
  const config = {
68
117
  domain: answers.domain.trim(),
118
+ apiPath: normalizeApiPath(answers.apiPath, answers.domain),
69
119
  token: answers.token.trim(),
70
120
  authType: answers.authType,
71
121
  email: answers.authType === 'basic' ? answers.email.trim() : undefined
@@ -83,9 +133,18 @@ function getConfig() {
83
133
  const envToken = process.env.CONFLUENCE_API_TOKEN;
84
134
  const envEmail = process.env.CONFLUENCE_EMAIL;
85
135
  const envAuthType = process.env.CONFLUENCE_AUTH_TYPE;
136
+ const envApiPath = process.env.CONFLUENCE_API_PATH;
86
137
 
87
138
  if (envDomain && envToken) {
88
139
  const authType = normalizeAuthType(envAuthType, Boolean(envEmail));
140
+ let apiPath;
141
+
142
+ try {
143
+ apiPath = normalizeApiPath(envApiPath, envDomain);
144
+ } catch (error) {
145
+ console.error(chalk.red(`❌ ${error.message}`));
146
+ process.exit(1);
147
+ }
89
148
 
90
149
  if (authType === 'basic' && !envEmail) {
91
150
  console.error(chalk.red('❌ Basic authentication requires CONFLUENCE_EMAIL.'));
@@ -95,6 +154,7 @@ function getConfig() {
95
154
 
96
155
  return {
97
156
  domain: envDomain.trim(),
157
+ apiPath,
98
158
  token: envToken.trim(),
99
159
  email: envEmail ? envEmail.trim() : undefined,
100
160
  authType
@@ -104,7 +164,7 @@ function getConfig() {
104
164
  if (!fs.existsSync(CONFIG_FILE)) {
105
165
  console.error(chalk.red('❌ No configuration found!'));
106
166
  console.log(chalk.yellow('Please run "confluence init" to set up your configuration.'));
107
- console.log(chalk.gray('Or set environment variables: CONFLUENCE_DOMAIN, CONFLUENCE_API_TOKEN, and CONFLUENCE_EMAIL.'));
167
+ console.log(chalk.gray('Or set environment variables: CONFLUENCE_DOMAIN, CONFLUENCE_API_TOKEN, CONFLUENCE_EMAIL, and optionally CONFLUENCE_API_PATH.'));
108
168
  process.exit(1);
109
169
  }
110
170
 
@@ -114,6 +174,7 @@ function getConfig() {
114
174
  const trimmedToken = (storedConfig.token || '').trim();
115
175
  const trimmedEmail = storedConfig.email ? storedConfig.email.trim() : undefined;
116
176
  const authType = normalizeAuthType(storedConfig.authType, Boolean(trimmedEmail));
177
+ let apiPath;
117
178
 
118
179
  if (!trimmedDomain || !trimmedToken) {
119
180
  console.error(chalk.red('❌ Configuration file is missing required values.'));
@@ -127,8 +188,17 @@ function getConfig() {
127
188
  process.exit(1);
128
189
  }
129
190
 
191
+ try {
192
+ apiPath = normalizeApiPath(storedConfig.apiPath, trimmedDomain);
193
+ } catch (error) {
194
+ console.error(chalk.red(`❌ ${error.message}`));
195
+ console.log(chalk.yellow('Please rerun "confluence init" to update your API path.'));
196
+ process.exit(1);
197
+ }
198
+
130
199
  return {
131
200
  domain: trimmedDomain,
201
+ apiPath,
132
202
  token: trimmedToken,
133
203
  email: trimmedEmail,
134
204
  authType
@@ -8,7 +8,8 @@ class ConfluenceClient {
8
8
  this.token = config.token;
9
9
  this.email = config.email;
10
10
  this.authType = (config.authType || (this.email ? 'basic' : 'bearer')).toLowerCase();
11
- this.baseURL = `https://${this.domain}/rest/api`;
11
+ this.apiPath = this.sanitizeApiPath(config.apiPath);
12
+ this.baseURL = `https://${this.domain}${this.apiPath}`;
12
13
  this.markdown = new MarkdownIt();
13
14
  this.setupConfluenceMarkdownExtensions();
14
15
 
@@ -23,6 +24,19 @@ class ConfluenceClient {
23
24
  });
24
25
  }
25
26
 
27
+ sanitizeApiPath(rawPath) {
28
+ const fallback = '/rest/api';
29
+ const value = (rawPath || '').trim();
30
+
31
+ if (!value) {
32
+ return fallback;
33
+ }
34
+
35
+ const withoutLeading = value.replace(/^\/+/, '');
36
+ const normalized = `/${withoutLeading}`.replace(/\/+$/, '');
37
+ return normalized || fallback;
38
+ }
39
+
26
40
  buildBasicAuthHeader() {
27
41
  if (!this.email) {
28
42
  throw new Error('Basic authentication requires an email address.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "A command-line interface for Atlassian Confluence with page creation and editing capabilities",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -10,6 +10,27 @@ describe('ConfluenceClient', () => {
10
10
  });
11
11
  });
12
12
 
13
+ describe('api path handling', () => {
14
+ test('defaults to /rest/api when path is not provided', () => {
15
+ const defaultClient = new ConfluenceClient({
16
+ domain: 'example.com',
17
+ token: 'no-path-token'
18
+ });
19
+
20
+ expect(defaultClient.baseURL).toBe('https://example.com/rest/api');
21
+ });
22
+
23
+ test('normalizes custom api paths', () => {
24
+ const customClient = new ConfluenceClient({
25
+ domain: 'cloud.example',
26
+ token: 'custom-path',
27
+ apiPath: 'wiki/rest/api/'
28
+ });
29
+
30
+ expect(customClient.baseURL).toBe('https://cloud.example/wiki/rest/api');
31
+ });
32
+ });
33
+
13
34
  describe('authentication setup', () => {
14
35
  test('uses bearer token headers by default', () => {
15
36
  const bearerClient = new ConfluenceClient({