confluence-cli 2.1.10 → 2.1.12

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.
@@ -135,7 +135,11 @@ class MacroConverter {
135
135
  buildUrl: this.buildUrl,
136
136
  webUrlPrefix: this.webUrlPrefix,
137
137
  });
138
- return walker.walk(storage);
138
+ const result = walker.walk(storage);
139
+ if (typeof options.onWarnings === 'function' && walker.warnings.length > 0) {
140
+ options.onWarnings(walker.warnings);
141
+ }
142
+ return result;
139
143
  }
140
144
  }
141
145
 
@@ -1,4 +1,4 @@
1
- const { parseDocument } = require('htmlparser2');
1
+ const { Parser, DomHandler } = require('htmlparser2');
2
2
  const { decodeHTML } = require('entities');
3
3
  const { fenceLength, cleanupWithFences } = require('./markdown-cleanup');
4
4
 
@@ -65,12 +65,57 @@ class StorageWalker {
65
65
 
66
66
  walk(storage) {
67
67
  this._depth = 0;
68
- const dom = parseDocument(storage, {
68
+ this.warnings = [];
69
+
70
+ // htmlparser2 in xmlMode is lenient: malformed input (unclosed tags,
71
+ // crossed nesting) is auto-closed without raising. We need to surface
72
+ // those events so callers can flag pages that were silently repaired.
73
+ //
74
+ // The handler emits onclosetag(name, isImplied) for *every* tag that
75
+ // wasn't matched by an explicit </tag> — including legitimate XML
76
+ // self-closing tags like `<br/>` and `<ri:attachment/>`. We
77
+ // distinguish the two by tracking each open tag's index range: a
78
+ // self-closing tag's open and close events share the same
79
+ // (startIndex, endIndex), while a genuinely auto-closed tag's close
80
+ // event lands at a later position in the source.
81
+ const handler = new DomHandler(null, { xmlMode: true });
82
+ const openStack = [];
83
+ const origOnOpenTag = handler.onopentag.bind(handler);
84
+ const origOnCloseTag = handler.onclosetag.bind(handler);
85
+ handler.onopentag = (...args) => {
86
+ openStack.push({ sIdx: parser.startIndex, eIdx: parser.endIndex });
87
+ origOnOpenTag(...args);
88
+ };
89
+ handler.onclosetag = (...args) => {
90
+ const [name, isImplied] = args;
91
+ const opened = openStack.pop();
92
+ if (isImplied) {
93
+ const selfClosing =
94
+ opened
95
+ && opened.sIdx === parser.startIndex
96
+ && opened.eIdx === parser.endIndex;
97
+ if (!selfClosing) {
98
+ const offset = parser.endIndex;
99
+ this.warnings.push({ type: 'implicit-close', tag: name, offset });
100
+ if (process.env.CONFLUENCE_CLI_VERBOSE) {
101
+ process.stderr.write(
102
+ `StorageWalker: auto-closed <${name}> at offset ${offset}\n`,
103
+ );
104
+ }
105
+ }
106
+ }
107
+ origOnCloseTag(...args);
108
+ };
109
+
110
+ const parser = new Parser(handler, {
69
111
  xmlMode: true,
70
112
  recognizeSelfClosing: true,
71
113
  decodeEntities: true,
72
114
  });
73
- return this.cleanup(this.walkNodes(dom.children));
115
+ parser.write(storage);
116
+ parser.end();
117
+
118
+ return this.cleanup(this.walkNodes(handler.dom));
74
119
  }
75
120
 
76
121
  walkNodes(nodes) {
@@ -250,9 +295,9 @@ class StorageWalker {
250
295
 
251
296
  handleExpand(node) {
252
297
  const titleParam = this.findParamByName(node, 'title');
298
+ const title = (titleParam ? this.getTextContent(titleParam) : '').trim();
253
299
  const body = this.getMacroBody(node);
254
- if (titleParam) {
255
- const title = this.getTextContent(titleParam);
300
+ if (title) {
256
301
  return `\n**EXPAND: ${title}**\n\n${this.walkNodes(body).trim()}\n\n**EXPAND_END**\n`;
257
302
  }
258
303
  return `\n<details>\n<summary>${this.labels.expandDetails || 'Expand Details'}</summary>\n\n${this.walkNodes(body).trim()}\n\n</details>\n`;
@@ -328,7 +373,7 @@ class StorageWalker {
328
373
 
329
374
  handleSharedBlock(node, type) {
330
375
  const blockKeyParam = this.findParamByName(node, 'shared-block-key');
331
- const blockKey = blockKeyParam ? this.getTextContent(blockKeyParam) : '';
376
+ const blockKey = (blockKeyParam ? this.getTextContent(blockKeyParam) : '').trim();
332
377
  const pageParam = this.findParamByName(node, 'page');
333
378
  if (pageParam && type === 'include-shared-block') {
334
379
  const acLink = this.findChildByName(pageParam, 'ac:link');
@@ -338,7 +383,8 @@ class StorageWalker {
338
383
  const pageTitle = this.escapeMarkdownText(decodeEntities(riPage.attribs['ri:content-title'] || ''));
339
384
  const includeLabel = this.labels.includeSharedBlock || 'Include Shared Block';
340
385
  const fromPageLabel = this.labels.fromPage || 'from page';
341
- return `\n> 📄 **${includeLabel}**: ${blockKey} (${fromPageLabel}: ${pageTitle} [link needs manual correction])\n`;
386
+ const keyPart = blockKey ? `: ${blockKey} ` : ' ';
387
+ return `\n> 📄 **${includeLabel}**${keyPart}(${fromPageLabel}: ${pageTitle} [link needs manual correction])\n`;
342
388
  }
343
389
  }
344
390
  }
@@ -346,8 +392,11 @@ class StorageWalker {
346
392
  // See handlePanel — trim to avoid bracketing `>` blank lines.
347
393
  const cleanContent = this.walkNodes(body).trim();
348
394
  const sharedLabel = this.labels.sharedBlock || 'Shared Block';
395
+ if (!blockKey && !cleanContent) return '';
396
+ const header = blockKey ? `**${sharedLabel}: ${blockKey}**` : `**${sharedLabel}**`;
397
+ if (!cleanContent) return `\n> ${header}\n`;
349
398
  const quoted = cleanContent.split('\n').map((line) => (line ? `> ${line}` : '>')).join('\n');
350
- return `\n> **${sharedLabel}: ${blockKey}**\n>\n${quoted}\n`;
399
+ return `\n> ${header}\n>\n${quoted}\n`;
351
400
  }
352
401
 
353
402
  handleViewFile(node) {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "2.1.10",
3
+ "version": "2.1.12",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "confluence-cli",
9
- "version": "2.1.10",
9
+ "version": "2.1.12",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "axios": "^1.15.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "2.1.10",
3
+ "version": "2.1.12",
4
4
  "description": "A command-line interface for Atlassian Confluence with page creation and editing capabilities",
5
5
  "main": "index.js",
6
6
  "bin": {