@bestend/confluence-cli 1.16.1 → 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.
@@ -1,16 +1,42 @@
1
1
  const axios = require('axios');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const FormData = require('form-data');
2
5
  const { convert } = require('html-to-text');
3
6
  const MarkdownIt = require('markdown-it');
4
7
  const { XMLValidator } = require('fast-xml-parser');
5
8
 
9
+ const NAMED_ENTITIES = {
10
+ // uppercase variants
11
+ aring: 'å', auml: 'ä', ouml: 'ö',
12
+ eacute: 'é', egrave: 'è', ecirc: 'ê', euml: 'ë',
13
+ aacute: 'á', agrave: 'à', acirc: 'â', atilde: 'ã',
14
+ oacute: 'ó', ograve: 'ò', ocirc: 'ô', otilde: 'õ',
15
+ uacute: 'ú', ugrave: 'ù', ucirc: 'û', uuml: 'ü',
16
+ iacute: 'í', igrave: 'ì', icirc: 'î', iuml: 'ï',
17
+ ntilde: 'ñ', ccedil: 'ç', szlig: 'ß', yuml: 'ÿ',
18
+ eth: 'ð', thorn: 'þ',
19
+ // uppercase variants
20
+ Aring: 'Å', Auml: 'Ä', Ouml: 'Ö',
21
+ Eacute: 'É', Egrave: 'È', Ecirc: 'Ê', Euml: 'Ë',
22
+ Aacute: 'Á', Agrave: 'À', Acirc: 'Â', Atilde: 'Ã',
23
+ Oacute: 'Ó', Ograve: 'Ò', Ocirc: 'Ô', Otilde: 'Õ',
24
+ Uacute: 'Ú', Ugrave: 'Ù', Ucirc: 'Û', Uuml: 'Ü',
25
+ Iacute: 'Í', Igrave: 'Ì', Icirc: 'Î', Iuml: 'Ï',
26
+ Ntilde: 'Ñ', Ccedil: 'Ç', Szlig: 'SS', Yuml: 'Ÿ',
27
+ Eth: 'Ð', Thorn: 'Þ'
28
+ };
29
+
6
30
  class ConfluenceClient {
7
31
  constructor(config) {
8
32
  this.domain = config.domain;
33
+ const rawProtocol = (config.protocol || 'https').trim().toLowerCase();
34
+ this.protocol = (rawProtocol === 'http' || rawProtocol === 'https') ? rawProtocol : 'https';
9
35
  this.token = config.token;
10
36
  this.email = config.email;
11
37
  this.authType = (config.authType || (this.email ? 'basic' : 'bearer')).toLowerCase();
12
38
  this.apiPath = this.sanitizeApiPath(config.apiPath);
13
- this.baseURL = `https://${this.domain}${this.apiPath}`;
39
+ this.baseURL = `${this.protocol}://${this.domain}${this.apiPath}`;
14
40
  this.markdown = new MarkdownIt();
15
41
  this.setupConfluenceMarkdownExtensions();
16
42
 
@@ -34,14 +60,17 @@ class ConfluenceClient {
34
60
  });
35
61
  }
36
62
 
37
- /**
38
- * Build a full page URL from a webui path (e.g., /spaces/SPACE/pages/123)
39
- * Context path is auto-detected from API responses.
40
- */
41
- pageUrl(path) {
42
- if (!path) return '';
43
- const context = this._contextPath || '';
44
- return `https://${this.domain}${context}${path}`;
63
+ sanitizeApiPath(rawPath) {
64
+ const fallback = '/rest/api';
65
+ const value = (rawPath || '').trim();
66
+
67
+ if (!value) {
68
+ return fallback;
69
+ }
70
+
71
+ const withoutLeading = value.replace(/^\/+/, '');
72
+ const normalized = `/${withoutLeading}`.replace(/\/+$/, '');
73
+ return normalized || fallback;
45
74
  }
46
75
 
47
76
  sanitizeStorageContent(content, options = {}) {
@@ -72,22 +101,9 @@ class ConfluenceClient {
72
101
  return result;
73
102
  }
74
103
 
75
- sanitizeApiPath(rawPath) {
76
- const fallback = '/rest/api';
77
- const value = (rawPath || '').trim();
78
-
79
- if (!value) {
80
- return fallback;
81
- }
82
-
83
- const withoutLeading = value.replace(/^\/+/, '');
84
- const normalized = `/${withoutLeading}`.replace(/\/+$/, '');
85
- return normalized || fallback;
86
- }
87
-
88
104
  buildBasicAuthHeader() {
89
105
  if (!this.email) {
90
- throw new Error('Basic authentication requires an email address.');
106
+ throw new Error('Basic authentication requires an email address or username.');
91
107
  }
92
108
 
93
109
  const encodedCredentials = Buffer.from(`${this.email}:${this.token}`).toString('base64');
@@ -110,12 +126,11 @@ class ConfluenceClient {
110
126
  return pageIdMatch[1];
111
127
  }
112
128
 
113
- // Handle /spaces/SPACEKEY/pages/PAGEID/Title format
114
- const spacesMatch = pageIdOrUrl.match(/\/spaces\/[^/]+\/pages\/(\d+)/);
115
- if (spacesMatch) {
116
- return spacesMatch[1];
129
+ const prettyMatch = pageIdOrUrl.match(/\/pages\/(\d+)(?:[/?#]|$)/);
130
+ if (prettyMatch) {
131
+ return prettyMatch[1];
117
132
  }
118
-
133
+
119
134
  // Handle display URLs - search by space and title
120
135
  const displayMatch = pageIdOrUrl.match(/\/display\/([^/]+)\/(.+)/);
121
136
  if (displayMatch) {
@@ -267,10 +282,11 @@ class ConfluenceClient {
267
282
  /**
268
283
  * Search for pages
269
284
  */
270
- async search(query, limit = 10) {
285
+ async search(query, limit = 10, rawCql = false) {
286
+ const cql = rawCql ? query : `text ~ "${String(query).replace(/"/g, '\\"')}"`;
271
287
  const response = await this.client.get('/search', {
272
288
  params: {
273
- cql: `text ~ "${query}"`,
289
+ cql,
274
290
  limit: limit
275
291
  }
276
292
  });
@@ -392,7 +408,7 @@ class ConfluenceClient {
392
408
  const webui = page._links?.webui || '';
393
409
  return {
394
410
  title: page.title,
395
- url: this.pageUrl(webui)
411
+ url: webui ? this.buildUrl(webui) : ''
396
412
  };
397
413
  }
398
414
  return null;
@@ -486,7 +502,7 @@ class ConfluenceClient {
486
502
  // Format: - [Page Title](URL)
487
503
  const childPagesList = childPages.map(page => {
488
504
  const webui = page._links?.webui || '';
489
- const url = this.pageUrl(webui);
505
+ const url = webui ? this.buildUrl(webui) : '';
490
506
  if (url) {
491
507
  return `- [${page.title}](${url})`;
492
508
  } else {
@@ -857,6 +873,158 @@ class ConfluenceClient {
857
873
  return downloadResponse.data;
858
874
  }
859
875
 
876
+ /**
877
+ * Upload an attachment to a page
878
+ */
879
+ async uploadAttachment(pageIdOrUrl, filePath, options = {}) {
880
+ if (!filePath || typeof filePath !== 'string') {
881
+ throw new Error('File path is required for attachment upload.');
882
+ }
883
+
884
+ const resolvedPath = path.resolve(filePath);
885
+ if (!fs.existsSync(resolvedPath)) {
886
+ throw new Error(`File not found: ${filePath}`);
887
+ }
888
+
889
+ const pageId = await this.extractPageId(pageIdOrUrl);
890
+ const form = new FormData();
891
+ form.append('file', fs.createReadStream(resolvedPath), { filename: path.basename(resolvedPath) });
892
+
893
+ if (options.comment !== undefined && options.comment !== null) {
894
+ form.append('comment', options.comment, { contentType: 'text/plain; charset=utf-8' });
895
+ }
896
+
897
+ if (typeof options.minorEdit === 'boolean') {
898
+ form.append('minorEdit', options.minorEdit ? 'true' : 'false');
899
+ }
900
+
901
+ const method = options.replace ? 'put' : 'post';
902
+ const response = await this.client.request({
903
+ url: `/content/${pageId}/child/attachment`,
904
+ method,
905
+ headers: {
906
+ ...form.getHeaders(),
907
+ 'X-Atlassian-Token': 'nocheck'
908
+ },
909
+ data: form,
910
+ maxBodyLength: Infinity,
911
+ maxContentLength: Infinity
912
+ });
913
+
914
+ const results = Array.isArray(response.data?.results)
915
+ ? response.data.results.map((item) => this.normalizeAttachment(item))
916
+ : [];
917
+
918
+ return {
919
+ results,
920
+ raw: response.data
921
+ };
922
+ }
923
+
924
+ /**
925
+ * Delete an attachment by ID
926
+ */
927
+ async deleteAttachment(pageIdOrUrl, attachmentId) {
928
+ if (!attachmentId) {
929
+ throw new Error('Attachment ID is required.');
930
+ }
931
+
932
+ const pageId = await this.extractPageId(pageIdOrUrl);
933
+ await this.client.delete(`/content/${pageId}/child/attachment/${attachmentId}`);
934
+ return { id: String(attachmentId), pageId: String(pageId) };
935
+ }
936
+
937
+ /**
938
+ * List content properties for a page with pagination support
939
+ */
940
+ async listProperties(pageIdOrUrl, options = {}) {
941
+ const pageId = await this.extractPageId(pageIdOrUrl);
942
+ const limit = this.parsePositiveInt(options.limit, 25);
943
+ const start = this.parsePositiveInt(options.start, 0);
944
+ const params = { limit, start };
945
+
946
+ const response = await this.client.get(`/content/${pageId}/property`, { params });
947
+ const results = Array.isArray(response.data.results) ? response.data.results : [];
948
+
949
+ return {
950
+ results,
951
+ nextStart: this.parseNextStart(response.data?._links?.next)
952
+ };
953
+ }
954
+
955
+ /**
956
+ * Fetch all content properties for a page, honoring an optional maxResults cap
957
+ */
958
+ async getAllProperties(pageIdOrUrl, options = {}) {
959
+ const pageSize = this.parsePositiveInt(options.pageSize || options.limit, 25);
960
+ const maxResults = this.parsePositiveInt(options.maxResults, null);
961
+ let start = this.parsePositiveInt(options.start, 0);
962
+ const properties = [];
963
+
964
+ let hasNext = true;
965
+ while (hasNext) {
966
+ const page = await this.listProperties(pageIdOrUrl, {
967
+ limit: pageSize,
968
+ start
969
+ });
970
+ properties.push(...page.results);
971
+
972
+ if (maxResults && properties.length >= maxResults) {
973
+ return properties.slice(0, maxResults);
974
+ }
975
+
976
+ hasNext = page.nextStart !== null && page.nextStart !== undefined;
977
+ if (hasNext) {
978
+ start = page.nextStart;
979
+ }
980
+ }
981
+
982
+ return properties;
983
+ }
984
+
985
+ /**
986
+ * Get a single content property by key
987
+ */
988
+ async getProperty(pageIdOrUrl, key) {
989
+ const pageId = await this.extractPageId(pageIdOrUrl);
990
+ const response = await this.client.get(`/content/${pageId}/property/${encodeURIComponent(key)}`);
991
+ return response.data;
992
+ }
993
+
994
+ /**
995
+ * Set (create or update) a content property
996
+ */
997
+ async setProperty(pageIdOrUrl, key, value) {
998
+ const pageId = await this.extractPageId(pageIdOrUrl);
999
+ const encodedKey = encodeURIComponent(key);
1000
+
1001
+ let version = 1;
1002
+ try {
1003
+ const existing = await this.client.get(`/content/${pageId}/property/${encodedKey}`);
1004
+ version = existing.data.version.number + 1;
1005
+ } catch (err) {
1006
+ if (!err.response || err.response.status !== 404) {
1007
+ throw err;
1008
+ }
1009
+ }
1010
+
1011
+ const response = await this.client.put(`/content/${pageId}/property/${encodedKey}`, {
1012
+ key,
1013
+ value,
1014
+ version: { number: version }
1015
+ });
1016
+ return response.data;
1017
+ }
1018
+
1019
+ /**
1020
+ * Delete a content property by key
1021
+ */
1022
+ async deleteProperty(pageIdOrUrl, key) {
1023
+ const pageId = await this.extractPageId(pageIdOrUrl);
1024
+ await this.client.delete(`/content/${pageId}/property/${encodeURIComponent(key)}`);
1025
+ return { pageId: String(pageId), key };
1026
+ }
1027
+
860
1028
  /**
861
1029
  * Convert markdown to Confluence storage format
862
1030
  */
@@ -872,36 +1040,39 @@ class ConfluenceClient {
872
1040
  * Convert HTML to native Confluence storage format
873
1041
  */
874
1042
  htmlToConfluenceStorage(html) {
875
- // MarkdownIt already produces valid HTML tags (h1-h6, p, strong, em, ul, ol, table, etc.)
876
- // This function only transforms elements that need Confluence-specific handling:
877
- // - li content wrapped in p (Confluence requirement)
878
- // - pre/code → ac:structured-macro code blocks with CDATA
879
- // - blockquote → info/warning/note macros
880
- // - th/td content wrapped in p
881
- // - hr → self-closing hr /
882
1043
  let storage = html;
883
1044
 
884
- // Wrap li content in p tags (Confluence requirement)
1045
+ // Convert headings to native Confluence format
1046
+ storage = storage.replace(/<h([1-6])>(.*?)<\/h[1-6]>/g, '<h$1>$2</h$1>');
1047
+
1048
+ // Convert paragraphs
1049
+ storage = storage.replace(/<p>(.*?)<\/p>/g, '<p>$1</p>');
1050
+
1051
+ // Convert strong/bold text
1052
+ storage = storage.replace(/<strong>(.*?)<\/strong>/g, '<strong>$1</strong>');
1053
+
1054
+ // Convert emphasis/italic text
1055
+ storage = storage.replace(/<em>(.*?)<\/em>/g, '<em>$1</em>');
1056
+
1057
+ // Convert unordered lists
1058
+ storage = storage.replace(/<ul>(.*?)<\/ul>/gs, '<ul>$1</ul>');
885
1059
  storage = storage.replace(/<li>(.*?)<\/li>/g, '<li><p>$1</p></li>');
886
1060
 
1061
+ // Convert ordered lists
1062
+ storage = storage.replace(/<ol>(.*?)<\/ol>/gs, '<ol>$1</ol>');
1063
+
887
1064
  // Convert code blocks to Confluence code macro
888
1065
  storage = storage.replace(/<pre><code(?:\s+class="language-(\w+)")?>(.*?)<\/code><\/pre>/gs, (_, lang, code) => {
889
1066
  const language = lang || 'text';
890
- // Decode HTML entities inside code blocks before wrapping in CDATA
891
- let decoded = code
892
- .replace(/&amp;/g, '&')
893
- .replace(/&lt;/g, '<')
894
- .replace(/&gt;/g, '>')
895
- .replace(/&quot;/g, '"')
896
- .replace(/&#39;/g, '\'');
897
- decoded = decoded.replace(/\]\]>/g, ']]]]><![CDATA[>');
898
-
899
1067
  return `<ac:structured-macro ac:name="code">
900
1068
  <ac:parameter ac:name="language">${language}</ac:parameter>
901
- <ac:plain-text-body><![CDATA[${decoded}]]></ac:plain-text-body>
1069
+ <ac:plain-text-body><![CDATA[${code}]]></ac:plain-text-body>
902
1070
  </ac:structured-macro>`;
903
1071
  });
904
1072
 
1073
+ // Convert inline code
1074
+ storage = storage.replace(/<code>(.*?)<\/code>/g, '<code>$1</code>');
1075
+
905
1076
  // Convert blockquotes to appropriate macros based on content
906
1077
  storage = storage.replace(/<blockquote>(.*?)<\/blockquote>/gs, (_, content) => {
907
1078
  // Check for admonition patterns
@@ -928,14 +1099,22 @@ class ConfluenceClient {
928
1099
  }
929
1100
  });
930
1101
 
931
- // Wrap th/td content in p tags (Confluence requirement)
1102
+ // Convert tables
1103
+ storage = storage.replace(/<table>(.*?)<\/table>/gs, '<table>$1</table>');
1104
+ storage = storage.replace(/<thead>(.*?)<\/thead>/gs, '<thead>$1</thead>');
1105
+ storage = storage.replace(/<tbody>(.*?)<\/tbody>/gs, '<tbody>$1</tbody>');
1106
+ storage = storage.replace(/<tr>(.*?)<\/tr>/gs, '<tr>$1</tr>');
932
1107
  storage = storage.replace(/<th>(.*?)<\/th>/g, '<th><p>$1</p></th>');
933
1108
  storage = storage.replace(/<td>(.*?)<\/td>/g, '<td><p>$1</p></td>');
934
1109
 
935
- // Links: keep <a href> as-is (compatible with both Cloud and Server/Data Center)
1110
+ // Convert links
1111
+ 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>');
936
1112
 
937
- // Convert horizontal rules
938
- storage = storage.replace(/<hr\s*\/?>/g, '<hr />');
1113
+ // Remove horizontal rules (not needed in Confluence storage format)
1114
+ storage = storage.replace(/<hr\s*\/?>/g, '');
1115
+
1116
+ // Clean up any remaining HTML entities and normalize whitespace
1117
+ storage = storage.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
939
1118
 
940
1119
  return storage;
941
1120
  }
@@ -1154,12 +1333,12 @@ class ConfluenceClient {
1154
1333
  markdown = markdown.replace(/<ac:structured-macro ac:name="include"[^>]*>[\s\S]*?<ac:parameter ac:name="">[\s\S]*?<ac:link>[\s\S]*?<ri:page\s+ri:space-key="([^"]+)"\s+ri:content-title="([^"]+)"[^>]*\/>[\s\S]*?<\/ac:link>[\s\S]*?<\/ac:parameter>[\s\S]*?<\/ac:structured-macro>/g, (_, spaceKey, title) => {
1155
1334
  // Try to build a proper URL - if spaceKey starts with ~, it's a user space
1156
1335
  if (spaceKey.startsWith('~')) {
1157
- const spacePath = `/display/${spaceKey}/${encodeURIComponent(title)}`;
1158
- return `\n> 📄 **${labels.includePage}**: [${title}](${this.pageUrl(spacePath)})\n`;
1336
+ const spacePath = `display/${spaceKey}/${encodeURIComponent(title)}`;
1337
+ return `\n> 📄 **${labels.includePage}**: [${title}](${this.buildUrl(`/display/${spacePath}`)})\n`;
1159
1338
  } else {
1160
1339
  // For non-user spaces, we cannot construct a valid link without the page ID.
1161
1340
  // Document that manual correction is required.
1162
- return `\n> 📄 **${labels.includePage}**: [${title}](${this.pageUrl(`/spaces/${spaceKey}/pages/[PAGE_ID_HERE]`)}) _(manual link correction required)_\n`;
1341
+ return `\n> 📄 **${labels.includePage}**: [${title}](${this.buildUrl(`/spaces/${spaceKey}/pages/[PAGE_ID_HERE]`)}) _(manual link correction required)_\n`;
1163
1342
  }
1164
1343
  });
1165
1344
 
@@ -1354,6 +1533,9 @@ class ConfluenceClient {
1354
1533
  // Numeric HTML entities
1355
1534
  markdown = markdown.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)));
1356
1535
  markdown = markdown.replace(/&#x([0-9a-fA-F]+);/g, (_, code) => String.fromCharCode(parseInt(code, 16)));
1536
+
1537
+ // Clean up nordic alphabets and other named entities
1538
+ markdown = markdown.replace(/&([a-zA-Z]+);/g, (match, name) => NAMED_ENTITIES[name] || match);
1357
1539
 
1358
1540
  // Clean up extra whitespace for standard Markdown format
1359
1541
  // Remove trailing spaces from each line
@@ -1377,7 +1559,7 @@ class ConfluenceClient {
1377
1559
  */
1378
1560
  async createPage(title, spaceKey, content, format = 'storage', options = {}) {
1379
1561
  let storageContent = content;
1380
-
1562
+
1381
1563
  if (format === 'markdown') {
1382
1564
  storageContent = this.markdownToStorage(content);
1383
1565
  } else if (format === 'html') {
@@ -1412,7 +1594,7 @@ class ConfluenceClient {
1412
1594
  */
1413
1595
  async createChildPage(title, spaceKey, parentId, content, format = 'storage', options = {}) {
1414
1596
  let storageContent = content;
1415
-
1597
+
1416
1598
  if (format === 'markdown') {
1417
1599
  storageContent = this.markdownToStorage(content);
1418
1600
  } else if (format === 'html') {
@@ -1499,6 +1681,55 @@ class ConfluenceClient {
1499
1681
  return response.data;
1500
1682
  }
1501
1683
 
1684
+ /**
1685
+ * Move a page to a new parent location
1686
+ */
1687
+ async movePage(pageIdOrUrl, newParentIdOrUrl, newTitle = null) {
1688
+ // Resolve both IDs from URLs if needed
1689
+ const pageId = await this.extractPageId(pageIdOrUrl);
1690
+ const newParentId = await this.extractPageId(newParentIdOrUrl);
1691
+
1692
+ // Fetch current page
1693
+ const response = await this.client.get(`/content/${pageId}`, {
1694
+ params: { expand: 'body.storage,version,space' }
1695
+ });
1696
+ const { version, title, body, space } = response.data;
1697
+
1698
+ // Fetch new parent to get its space (for validation)
1699
+ const parentResponse = await this.client.get(`/content/${newParentId}`, {
1700
+ params: { expand: 'space' }
1701
+ });
1702
+ const parentSpace = parentResponse.data.space;
1703
+
1704
+ // Validate same space
1705
+ if (parentSpace.key !== space.key) {
1706
+ throw new Error(
1707
+ `Cannot move page across spaces. Page is in space "${space.key}" ` +
1708
+ `but new parent is in space "${parentSpace.key}". ` +
1709
+ 'Pages can only be moved within the same space.'
1710
+ );
1711
+ }
1712
+
1713
+ // Proceed with move
1714
+ const pageData = {
1715
+ id: pageId,
1716
+ type: 'page',
1717
+ title: newTitle || title,
1718
+ space: { key: space.key },
1719
+ body: {
1720
+ storage: {
1721
+ value: body.storage.value,
1722
+ representation: 'storage'
1723
+ }
1724
+ },
1725
+ version: { number: version.number + 1 },
1726
+ ancestors: [{ id: newParentId }]
1727
+ };
1728
+
1729
+ const updateResponse = await this.client.put(`/content/${pageId}`, pageData);
1730
+ return updateResponse.data;
1731
+ }
1732
+
1502
1733
  /**
1503
1734
  * Get page content for editing
1504
1735
  */
@@ -1826,6 +2057,13 @@ class ConfluenceClient {
1826
2057
  };
1827
2058
  }
1828
2059
 
2060
+ buildUrl(path) {
2061
+ if (!path) return '';
2062
+ const context = this._contextPath || '';
2063
+ const normalized = path.startsWith('/') ? path : `/${path}`;
2064
+ return `${this.protocol}://${this.domain}${context}${normalized}`;
2065
+ }
2066
+
1829
2067
  toAbsoluteUrl(pathOrUrl) {
1830
2068
  if (!pathOrUrl) {
1831
2069
  return null;
@@ -1835,8 +2073,7 @@ class ConfluenceClient {
1835
2073
  return pathOrUrl;
1836
2074
  }
1837
2075
 
1838
- const normalized = pathOrUrl.startsWith('/') ? pathOrUrl : `/${pathOrUrl}`;
1839
- return `https://${this.domain}${normalized}`;
2076
+ return this.buildUrl(pathOrUrl);
1840
2077
  }
1841
2078
 
1842
2079
  parseNextStart(nextLink) {
@@ -1863,3 +2100,4 @@ class ConfluenceClient {
1863
2100
  }
1864
2101
 
1865
2102
  module.exports = ConfluenceClient;
2103
+ module.exports.NAMED_ENTITIES = NAMED_ENTITIES;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bestend/confluence-cli",
3
- "version": "1.16.1",
3
+ "version": "2.0.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": {
@@ -20,13 +20,14 @@
20
20
  "wiki",
21
21
  "documentation"
22
22
  ],
23
- "author": "pchuri",
23
+ "author": "bestend",
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "axios": "^1.12.0",
27
27
  "chalk": "^4.1.2",
28
28
  "commander": "^11.1.0",
29
- "fast-xml-parser": "^4.5.3",
29
+ "fast-xml-parser": "^5.5.4",
30
+ "form-data": "^4.0.5",
30
31
  "html-to-text": "^9.0.5",
31
32
  "inquirer": "^8.2.6",
32
33
  "markdown-it": "^14.1.0",
@@ -54,5 +55,10 @@
54
55
  "bugs": {
55
56
  "url": "https://github.com/bestend/confluence-cli/issues"
56
57
  },
57
- "homepage": "https://github.com/bestend/confluence-cli#readme"
58
+ "homepage": "https://github.com/bestend/confluence-cli#readme",
59
+ "files": [
60
+ "bin/",
61
+ "lib/",
62
+ ".claude/skills/"
63
+ ]
58
64
  }
package/.eslintrc.js DELETED
@@ -1,23 +0,0 @@
1
- module.exports = {
2
- env: {
3
- es2021: true,
4
- node: true,
5
- jest: true
6
- },
7
- extends: [
8
- 'eslint:recommended'
9
- ],
10
- parserOptions: {
11
- ecmaVersion: 12,
12
- sourceType: 'module'
13
- },
14
- rules: {
15
- 'indent': ['error', 2],
16
- 'linebreak-style': ['error', 'unix'],
17
- 'quotes': ['error', 'single'],
18
- 'semi': ['error', 'always'],
19
- 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
20
- 'no-console': 'off',
21
- 'no-process-exit': 'off'
22
- }
23
- };
@@ -1,34 +0,0 @@
1
- ---
2
- name: Bug report
3
- about: Create a report to help us improve
4
- title: '[BUG] '
5
- labels: bug
6
- assignees: ''
7
-
8
- ---
9
-
10
- **Describe the bug**
11
- A clear and concise description of what the bug is.
12
-
13
- **To Reproduce**
14
- Steps to reproduce the behavior:
15
- 1. Run command '...'
16
- 2. See error
17
-
18
- **Expected behavior**
19
- A clear and concise description of what you expected to happen.
20
-
21
- **Screenshots**
22
- If applicable, add screenshots to help explain your problem.
23
-
24
- **Environment (please complete the following information):**
25
- - OS: [e.g. macOS, Windows, Linux]
26
- - Node.js version: [e.g. 18.17.0]
27
- - confluence-cli version: [e.g. 1.0.0]
28
- - Confluence version: [e.g. Cloud, Server 8.5]
29
-
30
- **Additional context**
31
- Add any other context about the problem here.
32
-
33
- **Error logs**
34
- If applicable, add error logs or stack traces.
@@ -1,26 +0,0 @@
1
- ---
2
- name: Feature request
3
- about: Suggest an idea for this project
4
- title: '[FEATURE] '
5
- labels: enhancement
6
- assignees: ''
7
-
8
- ---
9
-
10
- **Is your feature request related to a problem? Please describe.**
11
- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
-
13
- **Describe the solution you'd like**
14
- A clear and concise description of what you want to happen.
15
-
16
- **Describe alternatives you've considered**
17
- A clear and concise description of any alternative solutions or features you've considered.
18
-
19
- **Use case**
20
- Describe how this feature would be used and who would benefit from it.
21
-
22
- **Additional context**
23
- Add any other context or screenshots about the feature request here.
24
-
25
- **Implementation suggestions**
26
- If you have ideas about how this could be implemented, please share them here.
@@ -1,37 +0,0 @@
1
- ---
2
- name: General Feedback
3
- about: Share your thoughts, suggestions, or general feedback about confluence-cli
4
- title: '[FEEDBACK] '
5
- labels: feedback, enhancement
6
- assignees: ''
7
-
8
- ---
9
-
10
- ## 📝 Your Feedback
11
-
12
- Thank you for taking the time to share your thoughts about confluence-cli!
13
-
14
- ### What did you try to accomplish?
15
- A clear description of what you were trying to do with confluence-cli.
16
-
17
- ### How was your experience?
18
- Tell us about your experience using the tool:
19
- - What worked well?
20
- - What was confusing or difficult?
21
- - What features are you missing?
22
-
23
- ### Your environment
24
- - OS: [e.g. macOS, Windows, Linux]
25
- - Node.js version: [e.g. 18.17.0]
26
- - confluence-cli version: [e.g. 1.0.1]
27
- - Confluence instance: [e.g. Cloud, Server, Data Center]
28
-
29
- ### Feature requests or improvements
30
- What would make confluence-cli more useful for you?
31
-
32
- ### Additional context
33
- Anything else you'd like to share about your experience with confluence-cli?
34
-
35
- ---
36
-
37
- 💡 **Tip**: You can also join our [Discussions](https://github.com/pchuri/confluence-cli/discussions) for general questions and community chat!