@bestend/confluence-cli 1.15.5 → 1.15.7

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/bin/confluence.js CHANGED
@@ -146,6 +146,8 @@ program
146
146
  .option('-f, --file <file>', 'Read content from file')
147
147
  .option('-c, --content <content>', 'Page content as string')
148
148
  .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
149
+ .option('--validate-storage', 'Validate storage content (XML well-formed) before sending')
150
+ .option('--no-sanitize-storage', 'Disable auto-escaping raw ampersands in storage format')
149
151
  .action(async (title, spaceKey, options) => {
150
152
  const analytics = new Analytics();
151
153
  try {
@@ -166,7 +168,10 @@ program
166
168
  throw new Error('Either --file or --content option is required');
167
169
  }
168
170
 
169
- const result = await client.createPage(title, spaceKey, content, options.format);
171
+ const result = await client.createPage(title, spaceKey, content, options.format, {
172
+ validateStorage: options.validateStorage,
173
+ sanitizeStorage: options.sanitizeStorage
174
+ });
170
175
 
171
176
  console.log(chalk.green('✅ Page created successfully!'));
172
177
  console.log(`Title: ${chalk.blue(result.title)}`);
@@ -189,6 +194,10 @@ program
189
194
  .option('-f, --file <file>', 'Read content from file')
190
195
  .option('-c, --content <content>', 'Page content as string')
191
196
  .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
197
+ .option('--validate-storage', 'Validate storage content (XML well-formed) before sending')
198
+ .option('--no-sanitize-storage', 'Disable auto-escaping raw ampersands in storage format')
199
+ .option('--validate-storage', 'Validate storage content (XML well-formed) before sending')
200
+ .option('--no-sanitize-storage', 'Disable auto-escaping raw ampersands in storage format')
192
201
  .action(async (title, parentId, options) => {
193
202
  const analytics = new Analytics();
194
203
  try {
@@ -213,7 +222,10 @@ program
213
222
  throw new Error('Either --file or --content option is required');
214
223
  }
215
224
 
216
- const result = await client.createChildPage(title, spaceKey, parentId, content, options.format);
225
+ const result = await client.createChildPage(title, spaceKey, parentId, content, options.format, {
226
+ validateStorage: options.validateStorage,
227
+ sanitizeStorage: options.sanitizeStorage
228
+ });
217
229
 
218
230
  console.log(chalk.green('✅ Child page created successfully!'));
219
231
  console.log(`Title: ${chalk.blue(result.title)}`);
@@ -238,6 +250,8 @@ program
238
250
  .option('-f, --file <file>', 'Read content from file')
239
251
  .option('-c, --content <content>', 'Page content as string')
240
252
  .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
253
+ .option('--validate-storage', 'Validate storage content (XML well-formed) before sending')
254
+ .option('--no-sanitize-storage', 'Disable auto-escaping raw ampersands in storage format')
241
255
  .action(async (pageId, options) => {
242
256
  const analytics = new Analytics();
243
257
  try {
@@ -261,7 +275,10 @@ program
261
275
  content = options.content;
262
276
  }
263
277
 
264
- const result = await client.updatePage(pageId, options.title, content, options.format);
278
+ const result = await client.updatePage(pageId, options.title, content, options.format, {
279
+ validateStorage: options.validateStorage,
280
+ sanitizeStorage: options.sanitizeStorage
281
+ });
265
282
 
266
283
  console.log(chalk.green('✅ Page updated successfully!'));
267
284
  console.log(`Title: ${chalk.blue(result.title)}`);
@@ -1,6 +1,7 @@
1
1
  const axios = require('axios');
2
2
  const { convert } = require('html-to-text');
3
3
  const MarkdownIt = require('markdown-it');
4
+ const { XMLValidator } = require('fast-xml-parser');
4
5
 
5
6
  class ConfluenceClient {
6
7
  constructor(config) {
@@ -24,10 +25,32 @@ class ConfluenceClient {
24
25
  });
25
26
  }
26
27
 
27
- sanitizeStorageContent(content) {
28
- // Escape unescaped ampersands that would break XML (except already-escaped entities)
29
- // This is a best-effort sanitizer; users should still provide valid XHTML when using --format storage.
30
- return content.replace(/&(?![a-zA-Z]+;|#\d+;|#x[0-9a-fA-F]+;)/g, '&amp;');
28
+ sanitizeStorageContent(content, options = {}) {
29
+ const sanitize = options.sanitizeStorage !== false;
30
+ const validate = options.validateStorage === true;
31
+
32
+ let result = content;
33
+
34
+ if (sanitize) {
35
+ // Escape unescaped ampersands that would break XML (except already-escaped entities)
36
+ result = result.replace(/&(?![a-zA-Z]+;|#\d+;|#x[0-9a-fA-F]+;)/g, '&amp;');
37
+ }
38
+
39
+ if (validate) {
40
+ const validation = XMLValidator.validate(result, {
41
+ allowBooleanAttributes: true,
42
+ suppressEmptyNodeCheck: true,
43
+ ignoreAttributes: false
44
+ });
45
+
46
+ if (validation !== true) {
47
+ const { err } = validation;
48
+ const location = err?.line ? ` (line ${err.line}${err.col ? `, col ${err.col}` : ''})` : '';
49
+ throw new Error(`Storage content is not well-formed XML: ${err.msg}${location}`);
50
+ }
51
+ }
52
+
53
+ return result;
31
54
  }
32
55
 
33
56
  sanitizeApiPath(rawPath) {
@@ -1352,7 +1375,7 @@ class ConfluenceClient {
1352
1375
  /**
1353
1376
  * Create a new Confluence page
1354
1377
  */
1355
- async createPage(title, spaceKey, content, format = 'storage') {
1378
+ async createPage(title, spaceKey, content, format = 'storage', options = {}) {
1356
1379
  let storageContent = content;
1357
1380
 
1358
1381
  if (format === 'markdown') {
@@ -1363,7 +1386,7 @@ class ConfluenceClient {
1363
1386
  }
1364
1387
 
1365
1388
  if (format === 'storage') {
1366
- storageContent = this.sanitizeStorageContent(storageContent);
1389
+ storageContent = this.sanitizeStorageContent(storageContent, options);
1367
1390
  }
1368
1391
 
1369
1392
  const pageData = {
@@ -1387,7 +1410,7 @@ class ConfluenceClient {
1387
1410
  /**
1388
1411
  * Create a new Confluence page as a child of another page
1389
1412
  */
1390
- async createChildPage(title, spaceKey, parentId, content, format = 'storage') {
1413
+ async createChildPage(title, spaceKey, parentId, content, format = 'storage', options = {}) {
1391
1414
  let storageContent = content;
1392
1415
 
1393
1416
  if (format === 'markdown') {
@@ -1398,7 +1421,7 @@ class ConfluenceClient {
1398
1421
  }
1399
1422
 
1400
1423
  if (format === 'storage') {
1401
- storageContent = this.sanitizeStorageContent(storageContent);
1424
+ storageContent = this.sanitizeStorageContent(storageContent, options);
1402
1425
  }
1403
1426
 
1404
1427
  const pageData = {
@@ -1427,7 +1450,7 @@ class ConfluenceClient {
1427
1450
  /**
1428
1451
  * Update an existing Confluence page
1429
1452
  */
1430
- async updatePage(pageId, title, content, format = 'storage') {
1453
+ async updatePage(pageId, title, content, format = 'storage', options = {}) {
1431
1454
  // First, get the current page to get the version number and existing content
1432
1455
  const currentPage = await this.client.get(`/content/${pageId}`, {
1433
1456
  params: {
@@ -1449,7 +1472,7 @@ class ConfluenceClient {
1449
1472
  }
1450
1473
 
1451
1474
  if (format === 'storage') {
1452
- storageContent = this.sanitizeStorageContent(storageContent);
1475
+ storageContent = this.sanitizeStorageContent(storageContent, options);
1453
1476
  }
1454
1477
  } else {
1455
1478
  // If no new content, use the existing content
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bestend/confluence-cli",
3
- "version": "1.15.5",
3
+ "version": "1.15.7",
4
4
  "description": "A command-line interface for Atlassian Confluence with page creation and editing capabilities",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -26,6 +26,7 @@
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
30
  "html-to-text": "^9.0.5",
30
31
  "inquirer": "^8.2.6",
31
32
  "markdown-it": "^14.1.0",